Appearance
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-composedefines 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
| Approach | What it is | When to use it |
|---|---|---|
| Direct system install | Install PostgreSQL, Redis, etc. with apt or brew | One project, one version of each service, you are comfortable with system config files |
| Docker (single container) | Run one service in isolation with docker run | Quick experiments, trying a service before committing to a full setup |
| Docker Compose | Define multiple services and their relationships in a docker-compose.yml | Any 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 effectVerify the installation:
bash
docker --version
docker run hello-world
# The hello-world container prints a confirmation message and exitsWhat 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/tcpBreaking 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: devpasswordStop and remove it when done:
bash
docker stop pg-test
docker rm pg-testDocker 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 -vDocker 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.ymlalongside 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
| Mistake | How you notice it | The fix |
|---|---|---|
| Port already in use | docker: Error response from daemon: port is already allocated | Another 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 restart | Database is empty after docker compose down && docker compose up -d | You 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 socket | Your 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 immediately | docker ps shows no running container, docker ps -a shows exited | Check 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 up | docker system df shows gigabytes of unused images | Run 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 --volumesRun 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