Docker

Docker Compose for Local Development Environments

Using docker-compose.yml to spin up a full local stack — app, database, cache — with one command, and the patterns that make it actually pleasant to work with.

Marcus LeeJune 10, 20263 min read
Share:

Running a database, a cache, and your app together locally usually means either installing each one natively (version conflicts across projects, guaranteed) or hand-writing a handful of docker run commands you forget the flags to every time. Compose fixes both.

A typical setup

yaml
# docker-compose.yml
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://postgres:postgres@db:5432/myapp
    depends_on:
      - db
      - redis
    volumes:
      - .:/app
      - /app/node_modules
 
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: myapp
    ports:
      - "5432:5432"
    volumes:
      - db-data:/var/lib/postgresql/data
 
  redis:
    image: redis:7
    ports:
      - "6379:6379"
 
volumes:
  db-data:
bash
docker compose up          # start everything
docker compose up -d        # same, but detached (background)
docker compose down         # stop and remove containers
docker compose logs -f app  # follow logs for one service

One command brings up the app, a Postgres database, and Redis, all networked together automatically — services can reach each other by name (db, redis) without manually configuring a Docker network.

The volumes trick that avoids a common node_modules bug

yaml
volumes:
  - .:/app              # mount the whole project directory
  - /app/node_modules     # but NOT node_modules — use the container's own

Without the second line, mounting your entire project directory into the container also overwrites the container's node_modules with whatever's on your host machine — which can be the wrong platform's compiled binaries (e.g., host is macOS, container is Linux) and cause cryptic native-module errors. This pattern keeps the code live-synced for hot reload while letting the container manage its own dependencies.

Environment-specific overrides

yaml
# docker-compose.override.yml (automatically merged with docker-compose.yml)
services:
  app:
    command: npm run dev
    environment:
      NODE_ENV: development

Compose automatically merges docker-compose.override.yml on top of the base file if it exists — a clean way to keep local-only tweaks (like running a dev server with hot reload) separate from a base config that might also be used in CI.

Running one-off commands inside a service

bash
docker compose exec app npm run migrate
docker compose exec db psql -U postgres -d myapp

exec runs a command inside an already-running container — the way you'd run a database migration, open a database shell, or debug something without stopping the stack.

Waiting for dependencies to actually be ready

depends_on controls startup order, not readiness — Postgres's container can report "started" before it's actually accepting connections, which can cause the app to fail on its first connection attempt. For anything beyond local convenience, add a healthcheck:

yaml
services:
  db:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 2s
      timeout: 5s
      retries: 5
 
  app:
    depends_on:
      db:
        condition: service_healthy

This makes app actually wait until Postgres is verified ready to accept connections, not just "the container process started."

Why this beats installing everything natively

Every teammate runs the exact same Postgres version, the exact same Redis version, with zero risk of "works on my machine" caused by a locally installed version drifting from what production actually runs. Onboarding a new developer becomes git clone + docker compose up, instead of a setup document that's perpetually out of date.

Advertisement
Marcus Lee
Marcus Lee

Cloud & DevOps Engineer

Marcus covers Kubernetes, cloud infrastructure, and CI/CD. He's spent a decade running production systems at scale.

Related Articles