Docker Compose Patterns Every Developer Should Know

📅 April 27, 2026

import Post from ’../../layouts/Post.astro’; export const prerender = true;

Docker Compose is one of those tools people use for years without exploring what it can really do. Here are the patterns that will make your development workflow significantly better.

1. Health Checks — Because Starting Fast ≠ Ready Fast

services:
  postgres:
    image: postgres:16-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s

  api:
    depends_on:
      postgres:
        condition: service_healthy
    command: sh -c "npm run migrate && npm start"

condition: service_healthy blocks the api container until Postgres is genuinely ready — not just started.

2. Named Volumes for Persistent Dev Data

volumes:
  postgres_data:
  redis_data:

services:
  postgres:
    image: postgres:16-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

Data survives docker compose down -v if you remove the volume definition. Your DB doesn’t get wiped every time you restart.

3. Multiple Dockerfiles for Dev vs Prod

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    profiles: ["dev", "default"]

  api:build:
    build:
      context: .
      dockerfile: Dockerfile.optimized
    profiles: ["prod"]

Run docker compose --profile prod up for production builds, docker compose up for dev.

4. Resource Limits — Because One Container Shouldn’t Kill Your Machine

services:
  postgres:
    image: postgres:16-alpine
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 256M

  api:
    build: .
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M

5. env_file — Separate Secrets from Config

# .env (committed, with defaults)
DATABASE_HOST=localhost
LOG_LEVEL=info

# .env.local (gitignored, secrets)
DATABASE_PASSWORD=super_secret_change_me
JWT_SECRET=change_me_to_random_string

services:
  api:
    env_file:
      - .env
      - .env.local

Secrets in .env.local, defaults in .env. Nobody accidentally commits the database password.

6. Networks — Isolated Service Communication

services:
  frontend:
    networks:
      - web

  api:
    networks:
      - web
      - backend

  postgres:
    networks:
      - backend
    # Postgres is NOT reachable from frontend — only from api

networks:
  web:
    driver: bridge
  backend:
    driver: bridge
    internal: true  # No internet access — truly isolated

Internal networks are great for databases: services can reach the DB, but the DB can’t reach the internet.

7. The Override Pattern — Don’t Repeat Yourself

# docker-compose.yml — base config (committed)
version: '3.9'
services:
  api:
    build: .
    ports:
      - "3000:3000"
  postgres:
    image: postgres:16-alpine
# docker-compose.override.yml — dev overrides (gitignored)
services:
  api:
    environment:
      NODE_ENV: development
    volumes:
      - .:/app
    command: npm run dev
  postgres:
    ports:
      - "5432:5432"  # Expose DB port only in dev

docker compose up automatically reads override.yml. Prod uses only docker-compose.yml.

8. Wait-for Script — The Universal Health Check Pattern

#!/bin/bash
# wait-for.sh — put in ./scripts/wait-for.sh
host="$1"
shift
cmd="$@"

until nc -z "$host"; do
    echo "Waiting for $host..."
    sleep 1
done

exec $cmd
services:
  api:
    depends_on:
      postgres:
        condition: service_started
    command: ["./scripts/wait-for.sh", "postgres:5432", "npm", "start"]

Works for any service, any language, no healthcheck configuration needed.


These eight patterns cover the most common dev workflow headaches. Start with health checks and depends_on: condition: service_healthy — you’ll immediately stop seeing “connection refused” errors on startup.

💡

Enjoying the content? Here are tools I personally use and recommend:

  • 🌐 Hosting: Bluehost — what this blog runs on
  • 🛒 Tech Gear: My Amazon Store — keyboards, monitors, dev tools I use

Purchases through my links help keep this blog ad-free 💙

Enjoyed this post?

Subscribe to the newsletter or follow on YouTube for more dev content.

🎬 Watch Shorts