Use Cases (Updated: 6/1/2026)

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.

Claude Code Docker Compose Guide: Local App, Postgres, Redis, and Worker

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 caseWhy Compose fitsWatch out for
Local developmentOne command can start app, DB, Redis, and workerFile mount performance differs by OS
Integration testsTest DB and Redis can be created on demandCI ports and caches must be explicit
New teammate onboarding.env.example plus make setup fixes the pathNever put real secrets in examples
ProductionSometimes useful for small internal toolsRequires 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.

#Claude Code #Docker #Docker Compose #local development #PostgreSQL #Redis
Free

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.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.