Docker Compose for Local Development
Introduction¶
Docker Compose is at its best as a local development orchestrator: one
docker compose up brings up your app plus its database, cache, and message
queue, wired together, with your source code live-mounted. Here's a setup that
actually works day to day.
A realistic compose.yaml¶
services:
api:
build:
context: .
target: dev
command: npm run dev
volumes:
- ./src:/app/src # live reload
- /app/node_modules # don't shadow container's node_modules
ports:
- "3000:3000"
env_file: .env.dev
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: devpass
POSTGRES_DB: app
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 3s
timeout: 3s
retries: 10
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
worker:
build: { context: ., target: dev }
command: npm run worker
env_file: .env.dev
depends_on:
db: { condition: service_healthy }
profiles: ["workers"] # only starts with --profile workers
volumes:
db-data:
Key techniques¶
Healthchecks + depends_on: condition. Plain depends_on only waits for the
container to start, not to be ready. With condition: service_healthy the
API won't start until Postgres answers pg_isready. No more "connection
refused" on the first boot.
The node_modules anonymous volume. Bind-mounting ./ over /app hides the
node_modules the image built. The - /app/node_modules line (no host path)
keeps the container's copy.
Profiles. Services with a profiles: key are skipped unless you pass
--profile <name>. Keep heavy or occasional services (workers, mailhog,
elasticsearch) out of the default up.
Override files. compose.yaml is committed; compose.override.yaml is
auto-merged and can be gitignored for personal tweaks. docker compose -f
compose.yaml -f compose.prod.yaml for a different target.
Everyday commands¶
docker compose up -d # start in background
docker compose up --build api # rebuild just api
docker compose --profile workers up # include profiled services
docker compose logs -f api
docker compose exec db psql -U postgres app
docker compose down # stop + remove containers/networks
docker compose down -v # ...also delete named volumes (fresh DB)
docker compose ps
docker compose watch # sync files / rebuild on change (Compose 2.22+)
Verification and troubleshooting¶
depends_onignored — you're on Compose v1 (docker-compose, hyphen) or the long-formcondition:syntax needs Compose v2. Usedocker compose(no hyphen).- Code changes not reflected — bind mount path wrong, the process isn't a
watcher (
node dist/main.jsvsnpm run dev), or on macOS/Windows the FS event didn't propagate — usecompose watchor a polling watcher. node_moduleserrors after switching branches — the anonymous volume is stale.docker compose down -vordocker compose up --build --force-recreate.- Port already allocated — another compose project or a local service on
5432/6379. Change the host side of
ports:or stop the other. - DB data gone after
down— you randown -v, or you used an anonymous volume instead of the nameddb-data. - Healthcheck never passes — test command wrong for the image (
pg_isreadypath, missing shell in alpine).docker inspect --format '{{json .State.Health}}' <container>. - Slow bind-mount performance (macOS) — use
:delegated, or mount only what changes, or move tocompose watchsync.
Related tools and reading¶
- On-site: Web server config generator, YAML validator, Port Checker.
- Related posts: Docker volumes vs bind mounts, Container networking: bridge, host, none.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Docker & Kubernetes
StatefulSets Explained
StatefulSets give pods sticky names, sticky storage, and ordered operations — what databases and clustered apps need. Here's how each guarantee works.
October 24, 2026Persistent Volumes and Claims, Explained
PVC is a request, PV is the fulfilled storage, StorageClass is the factory. Most 'Pending PVC' problems are an access-mode or provisioner mismatch.
October 24, 2026Kubernetes Jobs and CronJobs
A Job runs pods until they succeed; a CronJob creates Jobs on a schedule. Here's parallelism, retry limits, the missed-schedule trap, and history cleanup.
October 24, 2026