Stage 4 · Provision
Docker Compose
Local Dev Environments
Mount source code, enable hot reload, seed databases, and run repeatable developer stacks.
Dev vs Production
Development containers prioritize fast iteration over optimization. You mount source code for live editing, enable hot reload, include debugging tools, and run database seeds. Production containers prioritize security, size, and reliability. Compose supports both workflows from the same codebase.
Bind Mounting Source
Bind mounts map your local source code into the container. When you edit a file on your host, the change appears inside the container immediately. No rebuild required — the container sees the updated file.
services:
api:
build:
context: .
target: development
volumes:
- ./src:/app/src # Source code
- /app/node_modules # Exclude node_modules from bind mount
ports:
- "3000:3000"
environment:
NODE_ENV: development
DATABASE_URL: postgres://postgres:secret@db:5432/app
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: app
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/dataThe anonymous volume /app/node_modules prevents the bind mount from overwriting the container's node_modules directory. Without this, your local (possibly empty) node_modules would replace the installed dependencies.
If your local node_modules is empty or different from the container's, bind mounting ./src:/app will overwrite /app. Use an anonymous volume for node_modules: - /app/node_modules.
Hot Reload
Hot reload detects file changes and automatically restarts the application or refreshes the browser. Combined with bind mounts, this creates a seamless development experience where changes appear instantly.
services:
# Node.js with nodemon
api:
build: .
command: npx nodemon --watch src --ext ts,js src/server.ts
volumes:
- ./src:/app/src
# Python with uvicorn
api:
image: python:3.12-slim
command: uvicorn main:app --reload --host 0.0.0.0
volumes:
- ./app:/code/app
# Go with air
api:
image: golang:1.22-alpine
command: air -c .air.toml
volumes:
- .:/app
working_dir: /appEach language ecosystem has its own hot reload tool: nodemon for Node.js, uvicorn --reload for Python, air for Go. The tool watches for file changes and restarts the server.
Database Seeding
Database seeding ensures every developer starts with the same data. Mount SQL scripts into the PostgreSQL entrypoint directory, or use Compose commands to run migrations and seeds after the database starts.
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: app
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
# Scripts in initdb.d run only on first start
- ./db/init:/docker-entrypoint-initdb.d:ro
seed:
image: postgres:16-alpine
depends_on:
db:
condition: service_healthy
command: >
psql -h db -U postgres -d app -c "
INSERT INTO users (name, email)
VALUES ('Demo', 'demo@example.com');
"
environment:
PGPASSWORD: secretThe initdb.d directory runs SQL and shell scripts when the database is first initialized. The seed service runs after the database is healthy and inserts test data.
Dev Tools
services:
adminer:
image: adminer:latest
ports:
- "8081:8080"
depends_on:
- db
redis-commander:
image: rediscommander/redis-commander:latest
environment:
REDIS_HOSTS: local:cache:6379
ports:
- "8082:8081"
mailhog:
image: mailhog/mailhog:latest
ports:
- "8025:8025" # Web UI
- "1025:1025" # SMTPAdminer provides a database UI. Redis Commander gives a Redis browser. MailHog captures outgoing emails for testing. These tools run only during development.
Reproducible Environments
The goal of containerized development is reproducibility. Every developer should run the same stack with the same versions. Pin image versions, include setup scripts, and document the environment in a README.
services:
api:
build: .
volumes:
- ./src:/app/src
- /app/node_modules
ports:
- "3000:3000"
environment:
NODE_ENV: development
command: npx nodemon src/server.ts
db:
image: postgres:16-alpine # Pinned version
environment:
POSTGRES_DB: app
POSTGRES_PASSWORD: secret
volumes:
- ./db/init:/docker-entrypoint-initdb.d:ro
ports:
- "5432:5432"
volumes:
pgdata:Pinning postgres:16-alpine ensures every developer uses the same version. The init scripts seed the database consistently. The anonymous volume preserves node_modules. This stack is identical on every machine.
Docker Compose watch (v2.22+) monitors files and syncs changes automatically. It is faster than bind mounts for some workflows and works across Docker Desktop and remote Docker hosts.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.