Docker Compose Tutorial: App + Database + Persistent Volumes
Model a small dev stack in docker-compose.yml: web service, Postgres, networks, env files, and healthchecks.
DevOps & infrastructure Intermediate 8 min read
·
Docker Compose turns a multi-container topology into declarative YAML so everyone runs the same ports, env vars, and volumes. Keys like depends_on and healthcheck exist to order startup: databases must accept connections before app code races to migrate.
Prerequisites: Install Docker on Linux. Data modeling: SQL basics.
Minimal compose file
Why a healthcheck on Postgres: Without it, the app container may start while Postgres is still initializing, causing flaky “connection refused” on first boot.
services:
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 5
web:
build: .
ports:
- "8080:8080"
environment:
DATABASE_URL: postgres://app:app@db:5432/app
depends_on:
db:
condition: service_healthy
volumes:
pgdata:
Why service DNS name db: Compose injects network aliases so DATABASE_URL can use hostname db instead of localhost (which would mean “inside the web container itself”).
Networks
Why the default network suffices for many stacks: All services on it resolve each other by service name without publishing every database port publicly.
Environment files
See environment variables and secrets.
Day-to-day commands
Why up --build: Rebuilds images when Dockerfiles or build context change; omit --build only when you know images are current.
docker compose up --build
docker compose logs -f web
docker compose down
docker compose down -v # also remove named volumes (wipes DB)
Warning: down -v deletes named volumes—only run when you intend to wipe data.
Production note
Compose is great for dev and small deployments; larger teams often move to Kubernetes or managed platforms with the same container images.
Frequently asked questions
docker-compose vs docker compose?
Modern Docker uses the Compose V2 plugin (docker compose). Standalone docker-compose still exists on older installs.
Why is my app “connection refused” to db?
Check service names, ports inside the network (use internal port 5432, not the host-mapped port), and healthchecks.
How do I run one-off commands?
docker compose run --rm web npm test spins a container for the command using the same image and env.
Can I use Compose with nginx?
Yes—add an nginx service and proxy to web; for TLS patterns see nginx reverse proxy guide.