Docker Compose
Docker Compose describes a multi-container application in a single YAML file: services, their images, environment, volumes, networks, and dependencies. One command starts the lot.
The problem it solves is that a modern application is not one process. A typical stack here is a frontend, an API, PostgreSQL, Redis, perhaps a Celery worker, behind Traefik. Starting those by hand in the right order with the right networking is tedious and easy to get subtly wrong, and every developer gets it wrong differently.
Where it is genuinely good
Development environments. docker compose up gives a new contributor the whole stack without installing anything but Docker. The version of Postgres in development matches production because it is the same image, which eliminates a familiar class of late surprise.
Single-host deployment. For a small service on one machine it is entirely adequate, and dramatically simpler than the alternatives. A great deal of production software runs perfectly well this way, and the pressure to move to Kubernetes is more often cultural than technical.
Where it stops
Compose has no answer for multiple hosts. There is no scheduling, no failover, no rolling deployment that keeps the service up. depends_on controls start order and not readiness — a container that has started is not a database that is accepting connections, and applications still need to retry their initial connection. This trips people constantly and the fix is in the application, not the compose file.
The transition point is when a single host stops being acceptable — because of availability requirements or load. Before then, adopting an orchestrator buys complexity without benefit.
Practical notes
Separate compose files for development and production. Development mounts source directories for hot reload and exposes database ports; production must do neither. An override file layered on a shared base keeps the difference explicit rather than conditional.
Named volumes for data. A bind-mounted database directory acquires host permission problems, and an anonymous volume gets orphaned and lost on recreation. Named volumes are the only option that behaves.
Secrets do not belong in the compose file. It is committed. Environment files excluded from git, or the secrets mechanism, and neither is a substitute for a real secret store beyond small deployments.
Running migrations deserves a decision rather than a default — as an entrypoint step it will run in every replica simultaneously, which is a race waiting to happen.
See also: Traefik, PostgreSQL, Celery, and devenv for the non-container approach to the same problem.