DEV Community

Cover image for Docker Compose Tricks That Will Make Your Local Dev 10x Smoother
Teguh Coding
Teguh Coding

Posted on

Docker Compose Tricks That Will Make Your Local Dev 10x Smoother

If you're still running services manually during development, we need to talk.

Docker Compose changed how I work. But it took me embarrassingly long to discover the features that actually matter. Not the basics — the tricks that make your local dev environment feel like production without the headaches.

Here are the ones I wish someone told me about sooner.

1. Use profiles to Organize Optional Services

Not every service needs to run all the time. Maybe you only need Redis when testing caching, or you only spin up Mailhog when working on email features.

services:
  app:
    build: .
    ports:
      - "3000:3000"

  redis:
    image: redis:7-alpine
    profiles: ["cache"]

  mailhog:
    image: mailhog/mailhog
    profiles: ["email"]
    ports:
      - "8025:8025"

  prometheus:
    image: prom/prometheus
    profiles: ["monitoring"]
Enter fullscreen mode Exit fullscreen mode
# Only start the app
docker compose up

# Start app + Redis
docker compose --profile cache up

# Start everything
docker compose --profile cache --profile email --profile monitoring up
Enter fullscreen mode Exit fullscreen mode

No more commenting/uncommenting services in your compose file.

2. depends_on with Health Checks (Stop Guessing)

Ever had your app crash because it started before the database was ready? This fixes that:

services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  app:
    build: .
    depends_on:
      db:
        condition: service_healthy
Enter fullscreen mode Exit fullscreen mode

Now your app waits until Postgres is actually accepting connections — not just until the container exists.

3. Hot Reload with watch (Compose 2.22+)

Forget bind mounts for development. Docker Compose now has built-in file watching:

services:
  app:
    build: .
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: ./package.json
Enter fullscreen mode Exit fullscreen mode
docker compose watch
Enter fullscreen mode Exit fullscreen mode
  • sync — file changes are synced instantly (like hot reload)
  • rebuild — container rebuilds when these files change (like when you add a new dependency)

No more docker compose up --build every time you change a file.

4. Override Files for Dev vs Production

Keep your base config clean. Use override files for environment-specific settings:

├── docker-compose.yml          # Base (shared)
├── docker-compose.override.yml # Dev (auto-loaded)
├── docker-compose.prod.yml     # Production
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml (base):

services:
  app:
    image: myapp:latest
    environment:
      NODE_ENV: production
Enter fullscreen mode Exit fullscreen mode

docker-compose.override.yml (dev — auto-loaded):

services:
  app:
    build: .
    volumes:
      - ./src:/app/src
    environment:
      NODE_ENV: development
      DEBUG: "true"
    ports:
      - "9229:9229"  # Node debugger
Enter fullscreen mode Exit fullscreen mode
# Dev (auto-loads override)
docker compose up

# Production (skip override)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up
Enter fullscreen mode Exit fullscreen mode

5. Shared Configs with extends and Anchors

Stop repeating yourself. Use YAML anchors for shared config:

x-common: &common
  restart: unless-stopped
  logging:
    driver: json-file
    options:
      max-size: "10m"
      max-file: "3"

services:
  api:
    <<: *common
    build: ./api
    ports:
      - "3000:3000"

  worker:
    <<: *common
    build: ./worker
    command: npm run worker
Enter fullscreen mode Exit fullscreen mode

Both services get the same restart policy and logging config. Change it in one place, applies everywhere.

6. .env Files (Keep Secrets Out of Compose)

Never hardcode credentials in your compose file:

.env:

POSTGRES_PASSWORD=supersecret
REDIS_URL=redis://redis:6379
API_KEY=sk-abc123
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml:

services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

  app:
    build: .
    env_file:
      - .env
Enter fullscreen mode Exit fullscreen mode

Add .env to .gitignore. Share a .env.example with dummy values instead.

7. One-off Commands Without Starting Everything

Need to run a migration, seed data, or debug something?

# Run a one-off command
docker compose run --rm app npm run migrate

# Open a shell in the app container
docker compose exec app sh

# Run database backup
docker compose exec db pg_dump -U postgres mydb > backup.sql
Enter fullscreen mode Exit fullscreen mode

The --rm flag removes the container after it finishes. Clean and simple.

8. Named Volumes for Persistent Data

Don't lose your database every time you docker compose down:

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

volumes:
  pgdata:
Enter fullscreen mode Exit fullscreen mode
# This preserves your data:
docker compose down

# This deletes everything (including volumes):
docker compose down -v
Enter fullscreen mode Exit fullscreen mode

Know the difference. It will save you hours of "why is my data gone?"

My Complete Dev Setup

Here's a real-world compose file I use daily:

services:
  app:
    build:
      context: .
      target: development
    ports:
      - "3000:3000"
      - "9229:9229"
    volumes:
      - ./src:/app/src
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    environment:
      DATABASE_URL: postgres://postgres:secret@db:5432/myapp
      REDIS_URL: redis://redis:6379

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"

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

volumes:
  pgdata:
Enter fullscreen mode Exit fullscreen mode

One docker compose up and I have my entire stack running. Every time. On any machine.

TL;DR

Trick What It Does
profiles Organize optional services
depends_on + healthcheck Wait for real readiness
watch Built-in hot reload
Override files Dev vs prod configs
YAML anchors DRY shared config
.env files Keep secrets safe
run --rm One-off commands
Named volumes Persistent data

Docker Compose isn't just for spinning up containers. When used right, it replaces your entire local dev setup script.


What's your favorite Docker Compose trick? I'm always looking for ways to improve my setup — share yours in the comments!

Top comments (0)