Claude Fable 5.1 & GPT-6 Astra packages are live

Docker Compose

Free

Compose for local development and small deployments — service dependencies and health, volumes, environment handling, and why it is not production…

203 lines7.9 KB Qwen DevOps
targetModels
Qwen3.8-MaxQwen3.8-Flash-NextQwen3.8-27BQwen3.8 FamilyFuture Qwen Models
name
docker-compose
category
DevOps
description
Compose for local development and small deployments — service dependencies and health, volumes, environment handling, and why it is not production orchestration.
license
MIT
author
Agent.md maintainers
last-verified
reviewed-by
unreviewed
<!-- Generated from models/_canonical by scripts/build-model-variants.js. Edit the canonical source, not this file. Behavioural profile for Qwen: scripts/model-profiles.json -->

#Task boundary

  1. Implement only what the task names; no extra abstractions or files.
  2. English-only comments and identifiers.
  3. Stop when the checklist passes.

#Purpose

Rules for Docker Compose. Its real value is local development parity: one command brings up the same database engine, cache and broker that production runs, so nobody debugs a SQLite-versus-Postgres difference.

It is not a production orchestrator — no rolling updates, no self-healing across hosts, no autoscaling. Use it for development, CI, and genuinely single-host deployments. → DevOps/kubernetes


#depends_on alone does not wait

yaml
services:
  api:
    build: .
    depends_on:
      db:    { condition: service_healthy }     # waits for the healthcheck
      redis: { condition: service_started }
    environment:
      DATABASE_URL: postgres://app:app@db:5432/app
    ports: ["3000:3000"]

  db:
    image: postgres:17.2-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 30s
    volumes: ["pgdata:/var/lib/postgresql/data"]

volumes: { pgdata: }

Bare depends_on waits for the container to start, not for the service to be usable. Postgres accepts connections seconds after the container starts, so the application crashes on boot and the failure looks random.

condition: service_healthy plus a real healthcheck is the fix. start_period matters: failures during it do not count toward retries, which is what allows a slow-starting database without a long total timeout.

Even with this, the application should retry its initial connection — dependencies restart, and Compose does not re-order anything when they do.


#Named volumes for state, bind mounts for source

MountUse forNote
Named volumeDatabase data, uploadsManaged by Docker, survives down
Bind mountSource code in developmentLive reload; slow on macOS/Windows
Anonymous volumeNothingAccumulates untracked, unnamed data
yaml
volumes:
  - .:/app                    # source, live-reloaded
  - /app/node_modules         # keep the container's install, not the host's

The node_modules line is the one people miss: without it the host directory shadows the container's, and native modules built for the host architecture fail inside the container.

docker compose down -v deletes named volumes. That is the intended reset command in development and a data-loss command anywhere else — never run it against anything you care about.


#Configuration and secrets

yaml
services:
  api:
    env_file: [.env]                       # local only, gitignored
    environment:
      DATABASE_URL: ${DATABASE_URL:?required}   # fail fast if unset
  1. Commit .env.example with every variable and a placeholder; never commit .env.
  2. ${VAR:?message} fails immediately with a clear error rather than starting with an empty value.
  3. Compose files are frequently committed, so no real secret belongs in one — not in environment, not in a build arg. → Security/secret-management
  4. Pin image tags (postgres:17.2-alpine), never latest. A colleague pulling latest next week gets a different database version and a different bug.

Use compose.override.yaml for local-only changes; it is merged automatically and can stay untracked, which keeps personal port choices out of the shared file.


#Ports, networks and isolation

  1. Publish only what you need on the host. ports: ["5432:5432"] exposes your development database on every interface — on a shared or public network that is an open database. Bind to loopback explicitly: "127.0.0.1:5432:5432".
  2. Services reach each other by service name on the default network (postgres://db:5432), with no published port required.
  3. Split networks when isolation matters: a backend network the database joins and the reverse proxy does not.
  4. Give each project a distinct name: so two checkouts do not collide on container and volume names.

#CI and single-host deployment

For CI, Compose is a reasonable way to stand up real dependencies:

bash
docker compose -f compose.yaml -f compose.ci.yaml up -d --wait
docker compose exec -T api npm test
docker compose down -v

--wait blocks until healthchecks pass, which removes the sleep 30 that otherwise appears in every pipeline. → Testing/integration

If you do deploy Compose to a single host, add what production needs:

  1. restart: unless-stopped on every service.
  2. Resource limits (deploy.resources.limits) so one container cannot take the host.
  3. Log rotation (logging.options.max-size), or the disk fills.
  4. A reverse proxy terminating TLS in front.
  5. Accept the constraint: docker compose up -d recreates containers, so there is a gap. There is no rolling update.

#Anti-patterns

Anti-patternWhy it failsFix
depends_on without a conditionWaits for start, not readinessservice_healthy plus a healthcheck
No healthcheckNothing to wait onReal readiness command
No start_periodSlow starters exhaust retriesSet it above cold-start time
No client-side connection retryDependency restarts break the appRetry with backoff
latest image tagsColleagues run different versionsPin the tag
Host node_modules shadowingNative modules fail in the containerAnonymous volume over the path
Anonymous volumes for stateUntracked data accumulatesNamed volumes
.env committedSecrets in version control.env.example only
Real secrets in the Compose fileCommitted and sharedRuntime injection
Unset variables silently emptyConfusing runtime failures${VAR:?}
ports bound to all interfacesDatabase exposed on the networkBind to 127.0.0.1
One flat networkNo isolation between tiersSeparate networks
down -v outside developmentDeletes all dataNever run it elsewhere
Compose as production orchestrationNo rolling updates or self-healingUse a real orchestrator
No log rotation on a host deploymentDisk fills; service diesmax-size and max-file
No resource limitsOne container takes the hostdeploy.resources.limits

#Checklist

  • Every dependency has a healthcheck with a realistic start_period
  • depends_on uses condition: service_healthy where readiness matters
  • The application retries its initial dependency connections
  • All image tags are pinned to specific versions
  • Stateful data uses named volumes; source uses bind mounts
  • Container-installed dependencies are shielded from host bind mounts
  • .env is gitignored and .env.example documents every variable
  • Required variables fail fast with ${VAR:?}
  • No real secret appears in any committed Compose file
  • Published ports are bound to loopback in development
  • Services communicate by service name over an internal network
  • Networks are split where tier isolation matters
  • The project declares a distinct name
  • CI uses --wait rather than sleeping
  • Host deployments set restart policies, resource limits and log rotation
  • The absence of rolling updates is understood and accepted, or Compose is not used