Compose for dev cheatsheet.

Dev override

# compose.yml (prod-ish baseline)
services:
  web:
    build: .
    image: myapp:latest
    environment:
      DATABASE_URL: postgres://postgres:x@db:5432/myapp

# compose.override.yml (auto-merged in dev)
services:
  web:
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      DEBUG: "1"
    command: npm run dev
    ports: ["3000:3000"]
docker compose up                # uses both files
docker compose -f compose.yml -f compose.prod.yml up   # for prod

Hot reload

services:
  web:
    volumes:
      - .:/app
      - /app/node_modules            # preserve image's
    command: npm run dev             # uses --watch

For Python:

services:
  web:
    volumes: [".:/app"]
    command: uvicorn main:app --reload --host 0.0.0.0

Anonymous volumes for node_modules

volumes:
  - .:/app              # bind source
  - /app/node_modules   # anonymous; hides bind for this path

Otherwise host’s node_modules (or absence) overrides image’s.

watch (compose v2.22+)

services:
  web:
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: ./package.json
docker compose watch

Replaces bind mount with file sync. Better cross-platform performance.

Build only on demand

services:
  web:
    image: myapp:dev
    build:
      context: .
      target: dev
docker compose build              # rebuild image
docker compose up                  # uses cached unless built

Multiple environments

docker compose --profile dev up
docker compose --profile prod -f compose.yml -f compose.prod.yml up
services:
  web:
    profiles: [dev, prod]
  
  mailhog:
    image: mailhog/mailhog
    profiles: [dev]
  
  prometheus:
    image: prom/prometheus
    profiles: [monitoring]

.env file

# .env (auto-loaded)
COMPOSE_PROJECT_NAME=myapp
TAG=v1
DATABASE_URL=postgres://postgres:x@db:5432/myapp
services:
  web:
    image: myapp:${TAG}
    env_file: .env

Helpful dev containers

services:
  db:
    image: postgres:16
    environment: { POSTGRES_PASSWORD: x }
    ports: ["5432:5432"]      # expose for local connections
    volumes: [pg_data:/var/lib/postgresql/data]
  
  redis:
    image: redis:7-alpine
    ports: ["6379:6379"]
  
  mailhog:
    image: mailhog/mailhog
    ports:
      - "1025:1025"           # SMTP
      - "8025:8025"           # UI
  
  adminer:
    image: adminer
    ports: ["8080:8080"]

volumes:
  pg_data:

Devcontainer (VS Code)

.devcontainer/devcontainer.json:

{
  "name": "myapp",
  "dockerComposeFile": "../compose.yml",
  "service": "web",
  "workspaceFolder": "/app",
  "customizations": {
    "vscode": {
      "extensions": ["ms-python.python", "esbenp.prettier-vscode"]
    }
  },
  "postCreateCommand": "npm install"
}

Open in VS Code → “Reopen in container.”

Debugger ports

services:
  web:
    ports:
      - "3000:3000"
      - "9229:9229"           # Node inspector
node --inspect=0.0.0.0:9229 server.js

Attach VS Code debugger to localhost:9229.

For Python (debugpy):

import debugpy
debugpy.listen(("0.0.0.0", 5678))
ports: ["5678:5678"]

docker compose exec for tooling

docker compose exec web npm test
docker compose exec web python manage.py migrate
docker compose exec db psql -U postgres myapp

Service-specific commands

services:
  shell:
    image: myapp:latest
    profiles: [tools]
    command: bash
    tty: true
    stdin_open: true
docker compose run --rm shell

Use compose for tests

services:
  test:
    build: .
    command: pytest
    depends_on:
      db: { condition: service_healthy }
docker compose run --rm test
docker compose down -v        # cleanup

Hot reload across multiple services

services:
  api:
    volumes: [".:/app"]
    command: uvicorn main:app --reload
  
  worker:
    volumes: [".:/app"]
    command: celery -A config worker -l info --autoreload

Code changes hit both.

Speed tips on Mac/Windows

  1. Use :cached consistency flag for big bind mounts.
  2. Use named volumes for node_modules, .venv, target/.
  3. Use docker compose watch (sync) instead of bind.
  4. OrbStack / Colima > Docker Desktop.

Common mistakes

  • Bind-mounting node_modules from host → mismatched native modules.
  • Production image used unchanged in dev — no hot reload.
  • Forgetting tty: true for interactive containers.
  • Ports conflict with host services.
  • command: in compose overriding CMD — confusion about which runs.

Read this next

If you want my dev compose + devcontainer setup, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .