Claude Code Docker Compose Guide: Local App, Postgres, Redis, and Worker
Practical Docker Compose guide for Claude Code with app, Postgres, Redis, worker, healthchecks, env files, and CI notes.
Docker Compose is the local development blueprint for running an app, database, Redis, and background worker together. Docker’s Compose documentation describes it as a way to define and run multi-container applications while managing services, networks, and volumes from one YAML file.
That is why it works so well with Claude Code. Instead of asking vaguely for “a Docker setup”, you can give Claude Code concrete files to inspect: compose.yaml, Dockerfile, .env.example, and Makefile. The result is easier to review and easier for a team to reproduce.
When Masa tested this pattern on a small Next.js app with a queue worker, the useful part was not a clever prompt. The useful part was deciding the boring details early: waiting for Postgres, persisting Redis, protecting node_modules, and making migration/test commands match CI.
This guide builds a copy-pasteable local setup with app + PostgreSQL + Redis + worker. Compose is excellent for local development, integration tests, and reproducible onboarding. Production is a separate decision: orchestration, secrets, monitoring, backups, security boundaries, and cost need their own review.
Architecture
The target setup looks like this:
flowchart LR
Dev["Developer"] --> App["app: web server"]
App --> Pg["postgres: database"]
App --> Redis["redis: cache and queue"]
Worker["worker: background jobs"] --> Pg
Worker --> Redis
App --> Volume["named volume: node_modules"]
Pg --> PgVol["named volume: postgres_data"]
Redis --> RedisVol["named volume: redis_data"]
app serves the web application. worker processes background jobs such as email, webhooks, image processing, or queue tasks. postgres and redis include healthchecks so dependent services wait for actual readiness, not just container startup.
For the official syntax, keep the Compose file reference and services reference nearby. They help Claude Code avoid stale examples from the old docker-compose era.
Where Compose Fits
| Use case | Why Compose fits | Watch out for |
|---|---|---|
| Local development | One command can start app, DB, Redis, and worker | File mount performance differs by OS |
| Integration tests | Test DB and Redis can be created on demand | CI ports and caches must be explicit |
| New teammate onboarding | .env.example plus make setup fixes the path | Never put real secrets in examples |
| Production | Sometimes useful for small internal tools | Requires a full orchestration, security, recovery, and cost review |
The production boundary matters. Compose is great for creating the same workbench on every developer machine. For production, compare it against ECS, Kubernetes, Cloud Run, Fly.io, Render, or your existing platform. Ask Claude Code to list migration constraints instead of assuming the local file is a deployment plan.
Copy-Paste compose.yaml
This example assumes a Node.js or Next.js project. Replace npm run dev, npm run worker:dev, and migration scripts with the commands in your own package.json.
# compose.yaml
services:
app:
build:
context: .
dockerfile: Dockerfile
target: dev
command: npm run dev -- --hostname 0.0.0.0
ports:
- "3000:3000"
env_file:
- .env
environment:
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?schema=public
REDIS_URL: redis://redis:6379
volumes:
- .:/workspace
- node_modules:/workspace/node_modules
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:3000/api/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
interval: 10s
timeout: 5s
retries: 12
start_period: 20s
worker:
build:
context: .
dockerfile: Dockerfile
target: dev
command: npm run worker:dev
env_file:
- .env
environment:
DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?schema=public
REDIS_URL: redis://redis:6379
volumes:
- .:/workspace
- node_modules:/workspace/node_modules
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
postgres:
image: postgres:16-alpine
ports:
- "5432:5432"
env_file:
- .env
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./docker/postgres/init:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes"]
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
volumes:
node_modules:
postgres_data:
redis_data:
Three details are worth calling out. First, containers reach each other by service name, so the app uses postgres:5432 and redis:6379, not localhost. Second, node_modules is a named volume so the bind mount for source code does not overwrite container-installed dependencies. Third, the doubled dollar signs in the Postgres healthcheck keep the variables for the container shell instead of Compose interpolation.
Dockerfile
Use one Dockerfile with separate development and production stages. Beginners often maintain this more safely than separate Dockerfiles for every mode.
# Dockerfile
# syntax=docker/dockerfile:1
FROM node:22-alpine AS base
WORKDIR /workspace
ENV NEXT_TELEMETRY_DISABLED=1
COPY package*.json ./
RUN npm ci
FROM base AS dev
EXPOSE 3000
CMD ["npm", "run", "dev", "--", "--hostname", "0.0.0.0"]
FROM base AS build
COPY . .
RUN npm run build
FROM node:22-alpine AS production
WORKDIR /workspace
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /workspace/.next ./.next
COPY --from=build /workspace/public ./public
USER node
EXPOSE 3000
CMD ["npm", "start"]
The dev stage does not copy source files because Compose mounts them. The build stage copies the full repository so CI and production images can build without depending on your local filesystem.
.env.example
Commit .env.example, not .env. The example file should teach the shape of configuration without leaking real credentials.
# .env.example
POSTGRES_USER=app
POSTGRES_PASSWORD=app_password
POSTGRES_DB=app_development
DATABASE_URL=postgresql://app:app_password@postgres:5432/app_development?schema=public
REDIS_URL=redis://redis:6379
NODE_ENV=development
PORT=3000
env_file sends variables into containers. Compose variable interpolation also reads the project .env file. Keep those two ideas distinct and keep the values aligned.
Makefile and One-Off Commands
A small Makefile removes ambiguity from daily operations. If your team does not use make, move the same commands into package.json scripts.
COMPOSE = docker compose --env-file .env -f compose.yaml
.PHONY: setup up up-d down restart logs ps app-shell db-shell redis-cli migrate seed test lint clean
setup:
cp .env.example .env
$(COMPOSE) build
up:
$(COMPOSE) up --build
up-d:
$(COMPOSE) up -d --build
down:
$(COMPOSE) down
restart:
$(COMPOSE) restart app worker
logs:
$(COMPOSE) logs -f app worker postgres redis
ps:
$(COMPOSE) ps
app-shell:
$(COMPOSE) exec app sh
db-shell:
$(COMPOSE) exec postgres psql -U app -d app_development
redis-cli:
$(COMPOSE) exec redis redis-cli
migrate:
$(COMPOSE) run --rm app npm run db:migrate
seed:
$(COMPOSE) run --rm app npm run db:seed
test:
$(COMPOSE) run --rm app npm test
lint:
$(COMPOSE) run --rm app npm run lint
clean:
$(COMPOSE) down --volumes --remove-orphans
Use exec for a container that is already running. Use run --rm for a fresh one-off command such as lint, tests, migrations, or seed data. This distinction makes local commands closer to CI.
Claude Code Review Prompt
Claude Code’s common workflows encourage breaking everyday work into focused tasks: understand, edit, test, and review. Apply the same habit to Compose reviews.
Review this repository's Docker Compose setup.
Files:
- compose.yaml
- Dockerfile
- .env.example
- package.json
- CI workflow files if present
Check:
1. app + postgres + redis + worker can run locally
2. healthchecks and depends_on are used correctly
3. named volumes and bind mounts are safe
4. .env.example contains no real secrets
5. migrate, seed, test, and lint one-off commands exist
6. CI risks around ports, caches, permissions, and startup waits
7. what must be reviewed before using this pattern in production
Constraints:
- follow the existing framework and package manager
- keep large refactors as proposals
- when editing, explain the reason and verification command
Keep this prompt near your CLAUDE.md so the team repeats the same review standard. For adjacent workflows, read the Dev Container guide and CI/CD setup guide.
Practical Use Cases
The first use case is first-day onboarding. If a teammate can run make up after copying .env.example, the README becomes shorter and support questions become more specific.
The second use case is local queue testing. Email, image processing, billing webhooks, and notification fan-out are usually worker concerns. Adding worker to Compose lets you reproduce jobs without pretending the web server does everything.
The third use case is integration testing. Many bugs pass in a SQLite shortcut and fail in Postgres. Running tests against a Compose-managed Postgres and Redis catches SQL dialect, migration, and queue behavior earlier.
The fourth use case is Claude Code infrastructure review. Humans often miss localhost mistakes, stale volumes, missing healthchecks, or accidental secrets in examples. A reusable prompt turns that review into a repeatable checklist.
Common Pitfalls
The most common mistake is connecting to localhost from inside the app container. In Compose networking, service names are DNS names. Use postgres and redis.
The next mistake is treating depends_on as a complete readiness solution. condition: service_healthy helps, but the app should still retry database connections and the worker should not outrun migrations.
Stale named volumes are another source of confusing bugs. If the schema changes but postgres_data remains, your local state may no longer match the repository. Use make clean for a fresh test, knowing it deletes local data.
Do not place real API keys in .env.example. Use sample values, and keep production secrets in Docker secrets, your cloud secret manager, or CI secret storage.
Finally, do not turn the local Compose file into a production plan by default. Development exposes ports for convenience. Production needs decisions around TLS, network exposure, database backup, Redis persistence, monitoring, vulnerability scanning, image provenance, permissions, and cost.
ClaudeCodeLab Training and Templates
If you want to adapt this setup to your own repository, start with the ClaudeCodeLab products and templates to standardize CLAUDE.md, review prompts, and setup runbooks. If the hard part is team rollout, Docker Desktop differences, CI/CD, permissions, or deciding where Compose stops and production orchestration begins, use the Claude Code training and consultation page.
Official references used here: Docker Compose, Compose file reference, services reference, and Claude Code common workflows.
After trying the setup from this article, Masa’s test project could start the app, Postgres, Redis, and worker from make up, and the healthchecks removed the usual race where the app starts before the database is ready. The remaining sharp edge was stale named volumes: old local data can make migrations look broken or fixed for the wrong reason. The practical conclusion is simple: Compose is a strong local development foundation, while production still deserves a separate security, operations, recovery, and cost review.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Obsidian to CLAUDE.md Workflow: Stop Re-explaining Context
Turn Obsidian working notes into concise CLAUDE.md operating notes that make Claude Code sessions easier to resume.
Claude Code Revenue CTA Routing: Send Articles to PDF, Gumroad, and Consultation
A Claude Code workflow for routing article readers to the free PDF, Gumroad products, or consultation by intent.
Claude Code Team Handoff Rules: Review Evidence, Permissions, Rollback, and Revenue Paths
A practical Claude Code handoff format for team review, proof, permission rules, rollback, free PDF, Gumroad, and consultation paths.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.