Docker Compose
Real applications are rarely one container. A typical web app needs the app itself, a database, a cache, maybe a queue and a worker. Docker Compose describes all of them in one YAML file and runs them with one command. It creates a private network where services find each other by name, attaches volumes so data survives restarts, and starts things in dependency order.
Compose is the standard tool for local development environments and integration tests. It's also used for small single-host deployments, though clusters are Kubernetes territory.
TL;DR
- A
compose.yamldeclares services (containers), networks, and volumes. docker compose upbuilds and starts everything;downstops and removes it (add-vto delete volumes).- Services on the same Compose network resolve each other by service name (
postgres:5432). - Use
depends_onwithcondition: service_healthyso apps wait for dependencies to be ready, not just started. - Use named volumes for data, bind mounts for live source code, and
.envfiles for configuration. - Profiles toggle optional services;
docker compose watchsyncs code changes into running containers.
Quick Example
An API with PostgreSQL and Redis for local development:
Core Concepts
Services
Each service becomes one or more containers from either an image or a build context. Services accept most docker run options: ports, environment, volumes, command, restart, resource limits, and healthchecks. docker compose up --scale worker=3 runs several replicas of a service.
Networking
Compose creates a default bridge network per project, and every service joins it. Built-in DNS resolves service names, so the API connects to postgres:5432, not localhost. ports publishes a container port to the host ("5432:5432") and is only needed for access from outside Compose, such as a database GUI on your laptop. See Docker networking for custom networks and isolation.
Volumes
- Named volumes (
pgdata:/var/lib/...) are managed by Docker and persist acrossdown/upuntil you rundown -v. Use them for database data. - Bind mounts (
./src:/app/src) map a host directory into the container. Use them for live code during development.
More in Docker volumes.
Startup Order and Health
depends_on alone only waits for the container to start, not for Postgres to accept connections. With condition: service_healthy, Compose waits until the dependency's healthcheck passes. Apps should still retry connections, since dependencies can restart later too.
Configuration and Overrides
- A
.envfile next tocompose.yamlsupplies variables for${VAR}interpolation;env_file:loads variables into a container. compose.override.yamlis merged automatically, which is useful for local-only tweaks. Pass additional files explicitly with-f compose.yaml -f compose.ci.yaml.include:pulls in other Compose files, so a monorepo can compose per-team stacks.
Profiles
Tag optional services with profiles: [tools] and they only start when you ask for them with docker compose --profile tools up. That's good for admin UIs, seeders, and load generators you don't always need.
Local Development Workflows
- Watch mode (
develop.watch) syncs changed files into the container, or rebuilds when dependencies change. It's faster and more portable than bind-mounting everything, especially on macOS and Windows. - Seed and migrate with a one-off service (
docker compose run --rm migrate) or a service withrestart: "no"that the appdepends_onwithcondition: service_completed_successfully. - Integration tests in CI:
docker compose up -d --waitblocks until every service is healthy, then your test suite runs against real dependencies. See integration testing.
Best Practices
Healthchecks on Every Dependency
Define healthchecks for databases, caches, and brokers, and use service_healthy conditions. It removes "works on the second try" flakiness from startup.
Don't Publish Ports You Don't Need
Services talk over the Compose network. Publish only what you access from the host, and bind to localhost ("127.0.0.1:5432:5432") so dev databases aren't exposed to your whole network.
Keep Secrets Out of compose.yaml
Commit a .env.example and gitignore the real .env. For anything sensitive beyond local dev, use Compose secrets: (mounted as files) or your platform's secret store.
Pin Image Versions
postgres:17 rather than postgres:latest, so everyone on the team and CI runs the same versions. Match production versions where possible.
Common Mistakes
Connecting to localhost From Inside a Container
Losing Data With down -v
docker compose down -v deletes named volumes, including your local database. Use plain down day to day, and reserve -v for when you really want a clean slate.
Using Compose as a Production Orchestrator at Scale
Compose runs on one host. It has no multi-node scheduling, no rolling updates across machines, and no self-healing beyond restart policies. For a single small server it's fine; beyond that, use a real orchestrator or a managed container service.
FAQ
Is it docker-compose or docker compose?
Use docker compose (with a space). Compose v2 is a Docker CLI plugin written in Go; the old Python docker-compose v1 is end-of-life. The file can be named compose.yaml (preferred) or docker-compose.yml, and the top-level version: key is obsolete.
Should I use Compose or Kubernetes for local development?
Compose for most teams: it's simpler, faster, and good enough to run dependencies. Kubernetes-in-a-box tools (kind, minikube, Tilt, Skaffold) make sense when you're developing Kubernetes-specific behavior such as operators, Helm charts, or ingress rules.
How do I run a one-off command?
docker compose run --rm api npm run migrate starts a fresh container for the service and removes it afterwards. docker compose exec api sh runs a command inside the already running container.
Can Compose files be used in production?
For single-host deployments, yes, with restart: unless-stopped, resource limits, and a reverse proxy in front. Some platforms also accept Compose files as input. Anything needing multiple hosts or zero-downtime rollouts should move to an orchestrator.
Related Topics
- Docker — The container platform overview
- Docker Networking — Bridge networks and service discovery
- Docker Volumes — Persisting data between runs
- Dockerfile Best Practices — Building the images Compose runs
- Docker vs Kubernetes — When to graduate to an orchestrator
- Integration Testing — Testing against real dependencies