Docker Compose for Local Development

A practical guide to Docker Compose for local dev — from basic service definitions to multi-service stacks with databases and caches.

Getting a new team member productive on day one is a good litmus test for your development setup. If it takes more than a git clone and a single command to get the app running locally, there is room for improvement. Docker Compose makes this achievable.

The Basic Setup

A minimal compose.yaml for a web app with a database:

services:
  app:
    build: .
    ports:
      - "8080:8080"
    volumes:
      - .:/app
    depends_on:
      - db
    environment:
      DATABASE_URL: postgres://dev:dev@db:5432/myapp

  db:
    image: postgres:16
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev
      POSTGRES_DB: myapp
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

Start everything with:

docker compose up

Adding Services

Most real applications need more than just an app server and a database. Here is how to add Redis for caching:

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

And a worker process:

  worker:
    build: .
    command: ./run-worker
    depends_on:
      - db
      - redis
    environment:
      DATABASE_URL: postgres://dev:dev@db:5432/myapp
      REDIS_URL: redis://redis:6379

Practical Tips

Use volumes for data persistence. Named volumes (like pgdata above) survive docker compose down. Use docker compose down -v only when you intentionally want a clean slate.

Use health checks. Instead of relying on depends_on alone (which only waits for the container to start, not for the service to be ready), add health checks:

  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dev"]
      interval: 5s
      timeout: 5s
      retries: 5

Keep .env out of version control. Use .env.example as a template and add .env to .gitignore.

Use profiles for optional services. Not everyone needs the monitoring stack running all the time:

  grafana:
    image: grafana/grafana
    profiles: [monitoring]
    ports:
      - "3000:3000"

Start it only when needed: docker compose --profile monitoring up.

What This Enables

A reproducible local environment means:

  • New team members are productive in minutes, not days.
  • “Works on my machine” stops being an excuse.
  • Code reviews can focus on logic instead of environment issues.

That last point matters more than it seems. When reviewers can docker compose up and test a PR locally, the quality of feedback goes up dramatically.