env.dev

Docker Compose Cheat Sheet — CLI, Health Checks & Watch

Docker Compose commands, compose.yaml keys, depends_on health conditions, profiles, and develop.watch live reload — plus the gotchas that break stacks.

By env.dev Updated

Docker Compose runs multi-container applications from a single compose.yaml. This reference covers the CLI as of Compose v5.5.0 (17 August 2026) — 38 subcommands, the file-schema keys you reach for weekly, and the startup-ordering rules that cause most "works on my machine" bugs. Every command here is docker compose (v2 CLI plugin, Go); the hyphenated docker-compose v1 was retired in June 2023 and is not covered.

When to reach for this

  • A service starts before its database is actually accepting connections and you need depends_on with a real health condition, not just start ordering.
  • You want file changes on the host to reach a running container without a manual rebuild — the develop.watch block.
  • You need to run one stack in several variants (dev tooling, seed jobs, observability) without maintaining separate files — profiles.

Which Commands Do You Actually Use?

CommandDescription
docker compose up -dCreate and start everything, detached
docker compose up --buildRebuild images first, then start
docker compose up --watchStart and live-sync files per develop.watch
docker compose downStop and remove containers and networks
docker compose down -vAlso delete named volumes — this drops your data
docker compose psList the containers in this project, with health
docker compose logs -f <svc>Follow the logs for one service
docker compose exec <svc> shShell into a running container
docker compose run --rm <svc> <cmd>One-off task in a fresh container
docker compose restart <svc>Restart without recreating
docker compose configPrint the fully resolved file with vars substituted
docker compose lsList every Compose project running on the host

docker compose config is the single most useful debugging command: it resolves every ${VAR}, merges override files, and shows you exactly what Compose will act on. Run it before blaming Docker.

The Rest of the Subcommands

CommandDescription
buildBuild or rebuild service images
createCreate containers without starting them
start / stopStart or stop existing containers
pause / unpauseSuspend and resume processes in containers
killSend SIGKILL rather than a graceful stop
rmRemove stopped service containers
pull / pushDownload or upload service images
imagesShow images used by the project
volumesList volumes declared by the project
portPrint the host binding for a container port
topShow running processes per service
statsLive resource usage per container
eventsStream container events as they happen
cpCopy files between host and service container
attachAttach to the stdio of a running service container
waitBlock until services stop, then return their exit code
scaleChange the replica count for a service
watchRun only the file-watch loop, without app logs
commitCreate an image from a changed container
exportExport a container filesystem as an archive
publishPublish a Compose application
bridgeConvert a Compose file to another format
versionPrint the Compose version

How Do You Run Only Part of the Stack?

PatternWhat it does
docker compose up api workerStart only these services (plus their dependencies)
docker compose up --no-deps apiStart api alone, skipping dependencies
docker compose --profile debug upActivate a profile, adding its services
COMPOSE_PROFILES=debug,seed docker compose upActivate several profiles via the environment
docker compose -f base.yaml -f prod.yaml upMerge override files, later wins
docker compose --project-name staging upRun an isolated second copy of the same stack

A service with a profiles: key is skipped unless that profile is active. A service with no profiles: key always runs. That asymmetry is the whole feature: put your optional extras behind a profile and leave the core stack bare.

How Do You Make a Service Wait for a Healthy Dependency?

Plain depends_on: [db] only waits for the container to start, not for Postgres to accept connections. That gap is why an API can still fail its first query on a cold start, intermittently, in a way that disappears the moment you retry. Use the long syntax with a condition:

ConditionWaits until
service_startedContainer has started — same as the short syntax
service_healthyThe healthcheck on the dependency passes
service_completed_successfullyDependency ran to completion with exit code 0
depends_on fieldMeaning
conditionOne of the three values above
restart: trueRestart this service when the dependency is updated (Compose 2.17+)
required: falseOnly warn if the dependency is missing (Compose 2.20+); defaults to true

service_completed_successfully is the one people forget — it is how you express "run migrations, then start the API" without a sleep loop in an entrypoint script.

Healthcheck Keys

KeyPurpose
testThe probe; a list starting with CMD, CMD-SHELL, or NONE
intervalTime between checks once running
timeoutHow long a single check may take
retriesConsecutive failures before the container is unhealthy
start_periodGrace window where failures do not count
start_intervalShorter probe gap during start_period (Compose 2.20.2+)
disableSet true to turn off a healthcheck inherited from the image

start_period plus start_interval is the combination that makes service_healthy fast instead of painful: give a slow database a 40s grace window, but probe it every second inside that window so you proceed the moment it is genuinely up.

How Do You Live-Reload Code Into a Container?

The develop.watch block replaces the old bind-mount-everything trick. Run it with docker compose up --watch, or docker compose watch to keep sync events out of your application logs.

Watch keyPurpose
pathRequired. Host file or directory to watch
actionRequired. What happens on change
targetWhere the path maps inside the container
ignorePatterns to exclude, relative to path
initial_syncBring files up to date before watching starts
actionBehaviourUse for
syncCopy changed files into the containerFrameworks with their own hot reload
rebuildBuild a new image and replace the containerCompiled languages, lockfile changes
sync+restartCopy files, then restart the containerConfig files that are read at boot

The common mistake is watching a directory that includes node_modules or a build output folder. Every write inside it triggers a sync, and the loop never settles — set ignore explicitly.

Where Do Environment Variables Fit?

Compose has five separate environment mechanisms that do not all reach the container, and the precedence between them is the most common source of missing-value bugs. Rather than duplicate it here, the Docker Compose environment variables guide maps every form — environment:, env_file:, .env, --env-file, and ${VAR} substitution — to its scope and precedence, and covers the COMPOSE_* variables that configure the CLI itself. The one-line version:

MechanismReaches the container?
environment:Yes — highest precedence
env_file:Yes — lower than environment: and the shell
.env (auto-loaded)No — Compose-file interpolation only
--env-fileNo — replaces .env for interpolation only

What Are the Common Gotchas?

SymptomCauseFix
Data disappeared after renaming the folderProject name defaults to the directory basename, so volumes are orphaned under the old prefixSet COMPOSE_PROJECT_NAME explicitly; find old volumes with docker volume ls
App cannot reach the database at localhostEach container has its own network namespaceUse the service name as the hostname: postgres://db:5432
Port is already allocatedAnother project or a host process holds the portdocker compose ls to find the other project, or change the host side of the mapping
Env changes are ignored after an editExisting containers keep the environment they were created withdocker compose up -d --force-recreate
Changes to compose.yaml do nothingA stale override file or COMPOSE_FILE is in playCheck docker compose config to see what is actually merged
Build is slow on every runLayer cache busted by copying source before installing dependenciesCopy lockfiles and install first, then copy the rest of the source

For the wider Docker command surface — run, image management, networking, volumes — see the Docker cheat sheet.

Was this helpful?

Frequently Asked Questions

What is the difference between docker compose and docker-compose?

docker compose (space) is the v2 CLI plugin written in Go and shipped with Docker. docker-compose (hyphen) was the Python v1 implementation, retired in June 2023. If a tutorial uses the hyphenated form, treat the rest of it as potentially stale too.

How do I make a service wait until the database is really ready?

Give the database a healthcheck, then use the long depends_on syntax with condition: service_healthy. Plain depends_on only waits for the container to start, which is why an API can still fail its first connection attempt.

Does docker compose down delete my database?

Not by default. down removes containers and networks but keeps named volumes. Adding -v (or --volumes) deletes them, which does drop your data.

How do I run two copies of the same stack at once?

Give each one a distinct project name: docker compose --project-name staging up, or set COMPOSE_PROJECT_NAME. Every container, network, and volume is prefixed with it, so the two stacks stay isolated.

What is the difference between docker compose up --watch and a bind mount?

A bind mount exposes a host directory to the container continuously, along with its permissions and platform quirks. develop.watch copies only the paths you list and can rebuild or restart on change rather than only syncing, so it handles compiled languages and boot-time config files that a mount cannot.