Compose Best Practices
Compose is the best tool for a fast, reproducible local development loop, and a poor tool for production orchestration. These practices keep it in its lane: reliable for dev and CI, honest about where Kubernetes takes over.
Search across all documentation pages
Compose is the best tool for a fast, reproducible local development loop, and a poor tool for production orchestration. These practices keep it in its lane: reliable for dev and CI, honest about where Kubernetes takes over.
Read each group and apply the bullets to your compose.yaml today.
Treat the practices as defaults, not laws. The theme is consistency across teammates and a clean boundary between dev tooling and real deployment.
compose.yaml. It is the current canonical name. Drop the obsolete top-level version: key, which the Compose Specification no longer uses.compose.yaml and local tweaks in compose.override.yaml, which Compose loads automatically.docker compose config. Before trusting a multi-file setup, print the resolved configuration and read what actually runs.profiles so a plain up stays lean.postgres:17 and redis:7, not latest, so everyone gets identical versions and CI stays deterministic.dev target with reload tooling from a lean runtime target, and select with build.target.docker compose up --build so pipelines never reuse a stale image from a previous run..dockerignore so node_modules, .git, and local artifacts do not bloat the build or leak into images.environment or env_file, never COPY a secret into a layer.db or cache, and let Docker DNS resolve it. Never hardcode container IPs.5432 or 6379 only when a host tool needs them."5433:5432", rather than fighting for the default port.up and down, and docker compose down --volumes gives you a clean reset when you need one.node_modules with an anonymous volume so container dependencies survive.depends_on with condition: service_healthy so apps wait for databases instead of racing them.--wait in automation. docker compose up --wait blocks until services are healthy, which is exactly what CI needs before running tests.docker compose run --rm app <migrate> after the database is healthy, reusing the app image and network.Your base file should be environment-agnostic, images pinned, services named not IP-addressed, and startup gated by healthchecks.
Optional tooling should sit behind profiles, state should live in named volumes, and production should be modeled in its own manifests.
If a plain docker compose up gives a new teammate a working stack in one command, and nobody mistakes it for production, you have it right.
Should I ever deploy with Compose? For a throwaway single-server demo, maybe. For anything needing resilience, scale, or zero-downtime updates, use Kubernetes.
How do I share settings across environments? Keep a neutral base and layer override or environment-specific files, verifying the result with docker compose config.
Why avoid latest tags? They drift silently between machines and over time, breaking reproducibility. Pin explicit versions.
How do I stop the app racing the database? Add a healthcheck to the database and condition: service_healthy to the app's depends_on.
Do I need published ports for services to talk? No. Internal service-name networking handles that. Ports are only for host access.
Is docker compose the same as docker-compose? No. The hyphenated form is the legacy v1 tool. Use Compose v2, invoked as docker compose.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).
Reviewed by Chris St. John·Last updated Jul 19, 2026