Skip to content

Docker & Local Services ​

Real applications depend on other programs. Your Python agent needs PostgreSQL for memory. Maybe it needs Redis for caching, or a message queue for background jobs. Installing all of these directly on your operating system works until you need two different versions of the same service, or until a system update breaks one of them, or until you want to tear everything down and start clean without hunting through config files scattered across /etc.

Docker solves this by packaging each service into an isolated container that includes everything it needs to run: the code, the runtime, the system libraries, and the configuration. Containers start and stop in seconds and do not leave configuration residue on your system.

What you'll learn

  • Docker packages a service and all its dependencies into a single container that runs identically everywhere
  • docker-compose defines multi-service setups in a YAML file -- one command starts your entire development stack
  • Use Docker when a service has complex dependencies or when you need to run multiple versions side by side

The problem ​

You install PostgreSQL directly on your laptop for one project. Then another project needs a different major version with incompatible configuration. You try to install both and end up with port conflicts, clashing config files, and a pg_hba.conf that you are afraid to touch because you cannot remember which project set which line. Eventually you start a third project and spend an afternoon reinstalling everything from scratch.

Docker lets each project define exactly which services it needs, which versions, and how they connect. When you are done with a project, you stop the containers. The services and their data are isolated per project, and none of it touches your system configuration.

Options & when to use each ​

ApproachWhat it isWhen to use it
Direct system installInstall PostgreSQL, Redis, etc. with apt or brewOne project, one version of each service, you are comfortable with system config files
Docker (single container)Run one service in isolation with docker runQuick experiments, trying a service before committing to a full setup
Docker ComposeDefine multiple services and their relationships in a docker-compose.ymlAny project with more than one external dependency. This is the default for the course

Build it ​

Install Docker ​

Docker Desktop is the easiest path on macOS and Windows (which includes WSL). On Linux, install Docker Engine directly.

macOS / Windows: Download Docker Desktop from docker.com. The installer handles everything.

Linux (Debian/Ubuntu):

bash
# Remove old versions if present
for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do
    sudo apt remove $pkg 2>/dev/null
done

# Add Docker's official repository
sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Add your user to the docker group so you can run containers without sudo
sudo usermod -aG docker $USER
# Log out and back in for group membership to take effect

Verify the installation:

bash
docker --version
docker run hello-world
# The hello-world container prints a confirmation message and exits

What Docker is (one paragraph) ​

Docker runs applications in containers. A container is not a virtual machine -- it shares the host's kernel and isolates only the userspace. Think of it as a process with its own filesystem, network stack, and resource limits. Images are the blueprints: a read-only template that defines what goes into the container (OS packages, application code, configuration). Containers are running instances of images. You build or pull an image once, then start as many containers from it as you need.

Run your first container ​

bash
# Pull the PostgreSQL image and start a container
docker run --name pg-test \
    -e POSTGRES_PASSWORD=devpassword \
    -e POSTGRES_DB=testdb \
    -p 5433:5432 \
    -d postgres:16

# Check it is running
docker ps
# CONTAINER ID   IMAGE          ...   STATUS         PORTS
# abc123def456   postgres:16    ...   Up 5 seconds   0.0.0.0:5433->5432/tcp

Breaking down the command:

  • --name pg-test: give the container a human-readable name
  • -e POSTGRES_PASSWORD=devpassword: set an environment variable inside the container
  • -e POSTGRES_DB=testdb: create a database on first start
  • -p 5433:5432: map host port 5433 to container port 5432. We use 5433 because you might already have a system PostgreSQL on 5432
  • -d: run in detached mode (background)
  • postgres:16: the image name and tag

Connect to it:

bash
# Using psql locally, targeting the container on port 5433
psql -h 127.0.0.1 -p 5433 -U postgres -d testdb
# Password: devpassword

Stop and remove it when done:

bash
docker stop pg-test
docker rm pg-test

Docker Compose: multi-service setups ​

For a project that needs PostgreSQL and maybe Redis, define the whole stack in a docker-compose.yml:

yaml
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: agent_user
      POSTGRES_PASSWORD: devpassword
      POSTGRES_DB: agent_memory
    ports:
      - "5433:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  pgdata:

The volumes section is important. Without it, your database data disappears when the container stops. The named volume pgdata persists data on your host filesystem, outside the container lifecycle.

bash
# Start everything
docker compose up -d

# View running services
docker compose ps

# View logs from all services
docker compose logs -f

# Stop everything
docker compose down

# Stop and delete volumes (wipes your database data)
docker compose down -v

Docker vs. system install for PostgreSQL ​

You now have two options for running PostgreSQL: system install (from the previous lesson) or Docker. Which should you use?

  • System install when PostgreSQL is your primary database across all projects, and you want it always running and auto-starting on boot.
  • Docker when you want per-project isolation, when you need a specific version that conflicts with your system install, or when you want to define the database as part of your project's infrastructure in docker-compose.yml alongside other services.

Both approaches are valid. The previous lesson teaches the system install because it is the simpler path when you are just learning. Docker is the next step when your stack grows beyond a single database.

What goes wrong ​

MistakeHow you notice itThe fix
Port already in usedocker: Error response from daemon: port is already allocatedAnother process (or container) is using that port. Use a different host port with -p: map 5434:5432 instead of 5433:5432. Find what is using a port: sudo lsof -i :5433
Data disappeared after container restartDatabase is empty after docker compose down && docker compose up -dYou did not define a volume. Add a volumes: section to your compose file. Without it, data lives inside the container and is destroyed when the container is removed
Permission denied (Linux)docker: permission denied while trying to connect to the Docker daemon socketYour user is not in the docker group, or you did not log out and back in. Run groups to check. If docker is not listed: sudo usermod -aG docker $USER, then log out completely and back in
Container exits immediatelydocker ps shows no running container, docker ps -a shows exitedCheck the logs: docker logs <container-name>. Common causes: the process inside the container crashed, or the container ran a command that finished and exited
Disk space filling updocker system df shows gigabytes of unused imagesRun docker system prune -a to remove unused images, containers, and networks. Add --volumes to also remove unused volumes (this deletes data -- be sure)

Docker cleanup (when disk space gets tight) ​

Docker caches images, stopped containers, and build layers. Over time this adds up. These commands reclaim space:

bash
# See how much space Docker is using
docker system df

# Remove stopped containers, unused networks, dangling images
docker system prune

# Same as above, plus all unused images (not just dangling)
docker system prune -a

# Also remove unused volumes (WARNING: this deletes volume data, including databases)
docker system prune -a --volumes

Run docker system prune every few weeks. Run the -a variant when you are low on disk and willing to re-pull images later. Be careful with --volumes -- it wipes named volume data permanently.

Confirm it worked ​

After installing Docker and starting a PostgreSQL container:

bash
# 1. Verify Docker daemon is running
docker info
# Should print system information without errors

# 2. Verify the container is running
docker ps
# Should list your postgres container with status "Up"

# 3. Connect to the containerized PostgreSQL
psql -h 127.0.0.1 -p 5433 -U postgres -d testdb -c "SELECT 1 AS connected;"
# Should print: connected = 1

# 4. Verify data persists across restarts
docker compose restart db
psql -h 127.0.0.1 -p 5433 -U postgres -d testdb -c "SELECT 1 AS still_here;"
# Should still work after restart

Next: Shell & Workspace Performance