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.