Docker Compose

On this page 7

Docker Compose is a tool for defining and running multi-container applications from a single declarative YAML file, usually named compose.yaml. In the field of containers within cloud infrastructure, it describes an application as a set of services, networks and volumes and lets one command create, start, stop or remove all of them together on a single Docker host.

Compose sits one level above individual containers. A Dockerfile describes how to build one container image, and the Docker engine runs one container at a time; Compose describes how several containers cooperate, which images they use, how they reach each other and which data they keep. It is the standard way to reproduce a complete application stack on a developer workstation or a small server.

How Docker Compose turns a file into running containers

Docker Compose works by reading the Compose file, resolving it into a full model and asking the Docker engine to make the real state match that model. The current implementation is a plugin of the Docker command line invoked as docker compose, written in Go; the older standalone docker-compose program written in Python is deprecated and no longer maintained.

When a user runs docker compose up, the tool merges every referenced file, interpolates variables from the shell and from a .env file, and builds or pulls the required images. It then creates a project-scoped default network, named volumes and finally the containers, each labeled with the project name. Because the labels record ownership, a later command can find, recreate or remove exactly the resources of that project.

Compose is declarative and convergent. Running up again after editing the file recreates only the services whose configuration changed and leaves the others running. This behavior makes the file the single source of truth for the stack, which can be versioned alongside the application code.

The following table lists the commands used most often in daily work and what each one changes.

CommandEffectTypical use
docker compose up -dCreates and starts all services in the backgroundStart the whole stack
docker compose downStops and removes containers and the default networkClean shutdown; add -v to delete volumes
docker compose buildBuilds images for services with a build sectionRebuild after code changes
docker compose logs -fStreams combined output of servicesDebug startup problems
docker compose execRuns a command in a running service containerOpen a shell or run migrations
docker compose configPrints the fully resolved modelVerify merges and variables
Core Docker Compose commands

Structure of the Compose file

A Compose file is organized into top-level elements, defined by the open Compose Specification. The legacy version key is now obsolete and ignored; current tooling infers the schema from the content itself.

ElementPurposeExample content
servicesDefines each container and its runtime settingsImage, ports, environment, healthcheck
networksDeclares virtual networks for service communicationA separate back-end network
volumesDeclares named persistent storageDatabase data directory
configsProvides non-sensitive configuration filesA web server configuration
secretsProvides sensitive values as filesDatabase password
Top-level elements of a Compose file

Inside a service, the most important keys are image or build, ports, environment, volumes, depends_on, healthcheck, restart and profiles. Services on the same network reach each other by service name through built-in DNS, so a web service connects to db:5432 rather than to a fixed IP address.

A frequent source of confusion is depends_on. By default it only controls start order, not readiness. To wait until a database actually accepts connections, the dependency needs condition: service_healthy together with a healthcheck on the database service.

Docker Compose occupies the space between running single containers by hand and operating a cluster orchestrator. The comparison below shows where its scope ends.

ToolScopeStrengthLimitation
docker runOne containerSimple and immediateLong commands, no stack definition
Docker ComposeOne host, many containersReadable, reproducible stack fileNo multi-node scheduling or failover
Docker SwarmSmall clusterAccepts a similar file formatSmaller ecosystem than Kubernetes
KubernetesLarge clustersSelf-healing, autoscaling, rich APIsMuch higher operational complexity
Docker Compose and neighboring tools

Compose files do not run on Kubernetes directly. Conversion tools can translate them into Kubernetes manifests, but the result usually needs manual work on probes, resource limits and ingress. Teams therefore treat Compose as a development and small-deployment tool and maintain separate manifests or Helm charts for production clusters.

Practical use and configuration choices

Compose is strongest wherever a repeatable multi-service environment matters more than high availability. Several features help adapt one definition to different situations without duplicating it.

  • Override files: compose.override.yaml is merged automatically, so development settings such as source-code bind mounts stay out of the base file.
  • Multiple files with -f: a production file can be layered on the base file explicitly.
  • Profiles: optional services such as an admin tool start only when their profile is activated.
  • Variable interpolation: values such as image tags come from the environment or a .env file.
  • Watch mode: docker compose watch syncs or rebuilds when source files change.

The decision table summarizes when Compose is a sound choice and when a different tool fits better.

SituationRecommended choiceReason
Local development of a multi-service appDocker ComposeOne command reproduces the full stack
Integration tests in a CI pipelineDocker ComposeDisposable, isolated dependencies per run
Small internal tool on one serverDocker Compose with restart policiesAdequate when brief downtime is acceptable
Production service needing failoverKubernetes or a managed platformCompose cannot move containers between hosts
When to choose Docker Compose

Common mistakes and pitfalls

Most problems with Docker Compose come from assumptions about readiness, data and secrets rather than from the tool itself.

SymptomCauseFix
Application fails to reach the database at startupdepends_on without a health conditionAdd a healthcheck and service_healthy
Database data disappearsdown -v removed named volumesOmit -v and back up volumes
Passwords visible in the repositorySecrets written into environmentUse secrets or an untracked .env file
Port conflict on the hostTwo projects publish the same host portChange the host port or drop the mapping
Old code keeps runningImage not rebuilt after changesRun up --build or use watch mode
Typical problems and their fixes

Another pitfall is publishing every port to the host. Services that only talk to each other need no ports entry at all; leaving internal services unpublished reduces the attack surface and avoids conflicts.

A non-technical view

Modern applications are rarely one program. A typical web shop needs a website, a database and perhaps a cache, each running in its own separate container. Starting them one by one, in the right order and with the right connections, is tedious and easy to get wrong.

Docker Compose works like a recipe card for the whole meal. The card lists every dish, what goes into it and how the dishes are served together. Anyone with the card and a kitchen can reproduce the same meal with one instruction. The key idea is one file that starts the entire application at once.

A concrete example

A team of four developers builds an online booking system made of a Python web service, a PostgreSQL database, a Redis cache and a background worker. Before using Compose, each new developer spent most of a day on setup, installing matching versions of every component, and test results differed between laptops.

The team writes one `compose.yaml` with four services, a named volume for the database and a healthcheck that runs pg_isready. The web service depends on the database with condition: service_healthy, and a development override mounts the source code for live reloading. The same file, without the override, starts disposable dependencies for integration tests in the CI pipeline.

The outcome is that a new developer runs the full stack in minutes with docker compose up, and tests behave the same locally and in CI. When the product later moves to a cluster, the team writes Kubernetes manifests for production but keeps Compose for development.