Docker Compose for Local Dev skill for Codex

One command up, every service healthy. Codex writes compose.yaml files where the app waits for a healthy database instead of crashing on boot, migrations run as one-shot services, rarely used tools sit behind profiles, and Compose Watch syncs code or rebuilds on lockfile changes. Ports bind to loopback, secrets stay out of the file, and the stack is proven with docker compose up --wait.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- docker-compose-dev

Installs to ~/.agents/skills/docker-compose-dev/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $docker-compose-dev, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Builds Docker Compose setups for local development - compose.yaml with healthchecked databases, depends_on conditions, profiles for optional services, Compose Watch for live sync and rebuilds, override files, env files and named volumes. Use when a project needs a one-command local stack, when the app starts before Postgres is ready, when bind mounts are slow or clobber node_modules, when optional tools (queues, mail catchers, admin UIs) should stay off by default, or when reviewing a compose file.

Use it when

  • A repo needs a one-command local stack for new contributors
  • Services race each other at startup
  • Optional dev tools should not start by default
  • Bind mounts are slow or overwrite installed dependencies

Use something else when

  • You are writing or optimising the image itself: use dockerfile-best-practices
  • Containers cannot reach each other and you need to debug Docker networking: use docker-networking
  • You want a full editor environment in a container: use devcontainers

How to check the result

docker compose config --quiet passes and docker compose up -d --wait exits zero with every service healthy, then a source edit shows up in the running app without a manual rebuild.

The mistake it prevents

Using depends_on without a condition and assuming it waits for the database to accept connections; it only orders container start.

~/.agents/skills/docker-compose-dev/SKILL.md

--- name: docker-compose-dev description: Builds Docker Compose setups for local development - compose.yaml with healthchecked databases, depends_on conditions, profiles for optional… license: MIT ---

Done well means a new contributor runs one command, every service reports healthy, the app only starts once its dependencies accept connections, code edits show up without a manual rebuild, and nothing in the file is a production secret.

Inspect first

  1. Read AGENTS.md and the README for how the project is run today and which commands tests use. Compose should wrap those commands, not replace them.
  2. Look for existing compose.yaml, docker-compose.yml, compose.override.yaml, .env, .env.example and Dockerfiles. Keep service names that scripts or CI reference.
  3. Check the Compose version: docker compose version. Watch needs 2.22+, env_file with required: needs 2.24+, start_interval in healthchecks needs 2.20+ with Docker Engine 25+. The legacy docker-compose (v1, Python) binary is dead; use docker compose.
  4. List each service’s dependencies (database, cache, queue, object storage, mail) and which are needed for everyday work versus occasionally.

Pulling images needs network. In a Codex sandbox with network off, write the file and validate it with docker compose config, then hand the up step to the user.

File layout

FilePurposeCommitted?
compose.yamlServices shared by everyoneYes
compose.override.yamlMerged automatically; personal or dev-only tweaksUsually no (gitignore it, ship compose.override.example.yaml)
.envVariable interpolation for the compose file itself and project nameNo; commit .env.example
compose.ci.yamlCI variant, used with -f compose.yaml -f compose.ci.yamlYes

Do not add a top-level version: key; it is obsolete and only produces a warning.

Reference compose.yaml

name: shop

x-app-env: &app-env
  DATABASE_URL: postgres://app:app@db:5432/app
  REDIS_URL: redis://cache:6379/0

services:
  web:
    build:
      context: .
      target: dev                # dev stage in the Dockerfile with dev deps installed
    command: npm run dev -- --host 0.0.0.0
    ports:
      - "127.0.0.1:3000:3000"    # bind to loopback, not every interface
    environment: *app-env
    env_file:
      - path: .env.local
        required: false
    depends_on:
      db:
        condition: service_healthy
        restart: true            # restart web if db is restarted by compose
      cache:
        condition: service_started
      migrate:
        condition: service_completed_successfully
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: package-lock.json
        - action: sync+restart
          path: ./config
          target: /app/config

  migrate:
    build: { context: ., target: dev }
    command: npm run db:migrate
    environment: *app-env
    depends_on:
      db: { condition: service_healthy }

  db:
    image: postgres:17
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
      POSTGRES_DB: app
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 20s
      start_interval: 1s

  cache:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s

  mail:
    image: axllent/mailpit:latest
    profiles: [tools]
    ports: ["127.0.0.1:8025:8025"]

  adminer:
    image: adminer
    profiles: [tools]
    ports: ["127.0.0.1:8081:8080"]

volumes:
  pgdata:

Postgres 18 images moved the data directory convention (mount at /var/lib/postgresql instead of .../data); check the image docs when bumping majors, and never reuse a volume across Postgres major versions without a dump and restore.

Decisions

SituationDo
App crashes on start because DB is not readyHealthcheck on the DB plus depends_on: condition: service_healthy. Keep retry logic in the app anyway
Migrations or seed must run firstOne-shot service, depended on with service_completed_successfully
Optional tools (mail catcher, admin UI, tracing)profiles: [tools]; start with docker compose --profile tools up
Code edits must appear livedevelop.watch with sync; use rebuild for lockfiles, sync+restart for config read at boot
Framework already hot-reloads from a bind mount and it is fast enoughBind mount ./src:/app/src is fine; never bind-mount over node_modules
Slow file I/O on macOS/Windows bind mountsPrefer Watch sync, or keep dependencies in an anonymous volume
Shared settings across servicesx- extension field plus YAML anchor, as above
Splitting a large stackTop-level include: of other compose files (2.20+)
Same image, different commandReuse build: and set command:; Compose builds once per unique config

Watch rules: sync copies changed files into the running container (the target path must be writable by the container user); rebuild rebuilds the image and recreates the container; sync+restart syncs then restarts. Add ignore: patterns for build output so the app’s own writes do not loop. Start with docker compose up --watch or docker compose watch.

Hard rules

  • Ports for dev services bind to 127.0.0.1, otherwise a laptop on café Wi-Fi exposes Postgres with password app.
  • Containers talk to each other by service name (db:5432), never localhost. The published host port is only for tools on the host.
  • depends_on without a condition only orders container start; it does not wait for readiness.
  • Healthcheck commands run inside the container: the binary (curl, pg_isready) must exist in that image. Slim images often lack curl.
  • env_file sets container environment; .env sets compose-file interpolation. They are different mechanisms and confuse people constantly.
  • Named volumes persist across down. docker compose down -v deletes them and all local data: confirm with the user before running it.
  • Do not put real API keys in compose.yaml. Use env_file with required: false and document variables in .env.example.
  • Keep dev images out of production: the dev target belongs to local compose only.

Workflow

- [ ] 1. Inventory services, ports, and existing commands
- [ ] 2. Write compose.yaml with healthchecks and depends_on conditions
- [ ] 3. Put optional services behind profiles; add .env.example
- [ ] 4. docker compose config --quiet (validates and merges)
- [ ] 5. docker compose up --wait (returns when healthy, fails otherwise)
- [ ] 6. Edit a source file and confirm the change appears via Watch
- [ ] 7. Document the commands in README or AGENTS.md

Verification

docker compose config --quiet && echo "config ok"
docker compose config --services
docker compose up -d --wait            # exits non-zero if any healthcheck fails
docker compose ps --format 'table {{.Service}}\t{{.Status}}'
docker compose logs --tail=50 web
docker compose exec web sh -c 'nc -z db 5432 && echo db reachable'   # if nc is in the image
docker compose --profile tools up -d mail && curl -sf http://127.0.0.1:8025 >/dev/null && echo mailpit up

If --wait fails, docker inspect --format '{{json .State.Health}}' <container> shows the last healthcheck outputs; the usual causes are a missing binary in the image or a start_period shorter than the service’s first boot.

Output

**Start:** `docker compose up --watch` (core) / `docker compose --profile tools up` (adds mailpit, adminer)
**Services:** web (3000), db postgres:17 (healthy), cache redis:7, migrate (one-shot)
**Live reload:** src synced, lockfile triggers rebuild, config restarts web
**Data:** named volume pgdata; reset with `docker compose down -v` (destroys local data)
**Verified:** config valid, up --wait healthy, edit to src/app.ts visible in 1 s

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Docker Compose for Local Dev"
  short_description: "One command up, every service healthy"
  default_prompt: "$docker-compose-dev Set up docker compose for this app with Postgres and Redis"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem