Suggest an editImprove this articleRefine the answer for “What is Docker Compose?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**Docker Compose** is a tool for defining and running multi-container applications declaratively in a single YAML file. One command (`docker compose up`) brings up your whole stack: services, networks, volumes. ```yaml # compose.yaml services: web: image: nginx:1.27-alpine ports: ["8080:80"] depends_on: [api] api: image: myapp:1.0 environment: DATABASE_URL: postgres://postgres:dev@db:5432/app db: image: postgres:16 environment: POSTGRES_PASSWORD: dev ``` ```bash docker compose up -d # start everything docker compose down # stop everything ``` **Key:** Compose makes a long `docker run` chain into a versioned, declarative file. The same file works on a teammate's laptop and a CI runner.Shown above the full answer for quick recall.Answer (EN)Image**Docker Compose** is the standard way to define and run multi-container Docker applications. Instead of typing a long `docker run` for each service plus a `docker network create` plus volume management, you describe the whole stack in a YAML file and bring it up with one command. ## Theory ### TL;DR - A YAML file (default name: `compose.yaml` or legacy `docker-compose.yml`) describes services, networks, and volumes for your app. - One command brings everything up (`docker compose up`), one tears it down (`docker compose down`). - Each service becomes a container; Compose creates a private bridge network so services find each other by name (`db`, `api`). - Standard for **local dev** and **single-host deploys**. For multi-host production, you usually go to Swarm or Kubernetes. - **`docker compose` (v2, Go plugin)** is current. **`docker-compose` (v1, Python)** is deprecated since 2023. ### Quick example ```yaml # compose.yaml services: web: image: nginx:1.27-alpine ports: - "8080:80" depends_on: - api api: build: ./api environment: DATABASE_URL: postgres://postgres:devpass@db:5432/app depends_on: db: condition: service_healthy db: image: postgres:16 environment: POSTGRES_PASSWORD: devpass POSTGRES_DB: app volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s volumes: pgdata: ``` ```bash $ docker compose up -d [+] Running 4/4 ✔ Network myapp_default Created ✔ Container myapp-db-1 Healthy ✔ Container myapp-api-1 Started ✔ Container myapp-web-1 Started ``` One command, three services, a network, and a volume. Reproducible on any machine that has Docker. ### The compose file's main blocks ```yaml services: # one block per container <name>: image: ... # OR build: ./path (build from a Dockerfile) container_name: ... # optional fixed name; otherwise <project>-<service>-<index> command: ["..."] # override CMD entrypoint: ["..."] # override ENTRYPOINT ports: ["8080:80"] # publish ports HOST:CONTAINER expose: ["5000"] # internal-only, no host publish environment: # env vars (map or list) KEY: value env_file: .env # env vars from a file volumes: # mounts (named volumes or bind mounts) - data:/var/lib/... - ./conf:/etc/conf:ro networks: [frontend, backend] depends_on: # start order + health gate <other-service>: condition: service_healthy restart: unless-stopped healthcheck: { test: [...], interval: 30s } deploy: # resource limits, replicas (works in Compose too since v2) resources: limits: { cpus: "0.5", memory: 512M } networks: # named networks (default: one bridge per project) frontend: backend: volumes: # named volumes data: ``` Most real apps use a subset. The 80% subset is `services` with `image`/`build`, `ports`, `environment`, `volumes`, `depends_on`. ### Lifecycle commands ```bash docker compose up # start (foreground, streams logs) docker compose up -d # start detached docker compose down # stop and remove containers + network docker compose down -v # also delete volumes (DESTRUCTIVE) docker compose ps # list services for this project docker compose logs -f # tail logs from all services docker compose logs api # only one service docker compose exec api sh # shell into a running service docker compose run --rm api npm test # run a one-off command in a new container docker compose build # rebuild images for services with build: docker compose pull # pull images for services with image: docker compose restart api # restart one service docker compose stop / start # without removing docker compose config # validate and print resolved config ``` The project name comes from the directory name by default. Override with `-p` flag or `COMPOSE_PROJECT_NAME` env var. ### How services find each other Compose creates a default bridge network per project. All services join it automatically. Inside that network, **service names resolve via Docker's embedded DNS**: ```yaml services: web: image: myapp environment: DB_HOST: db # ← just "db", not localhost or an IP db: image: postgres:16 ``` From inside the `web` container, `ping db` works. The hostname `db` resolves to the db container's IP. No `localhost`, no `host.docker.internal`, no manual networking. ### v1 vs v2 (and why this matters in 2026) - **`docker-compose` (v1)** was a Python tool, separate from the Docker daemon. Hyphenated. Deprecated since July 2023. - **`docker compose` (v2)** is a Go-based plugin built into modern Docker. Space, not hyphen. **The current standard.** If you see `docker-compose` in old tutorials, the commands are nearly identical, but you should be running `docker compose` today. Most distros ship v2 by default; v1 is no longer maintained. ### Common mistakes **Confusing `down` with `stop`** ```bash docker compose stop # stops containers; everything still exists docker compose down # stops AND removes containers + the project network docker compose down -v # also wipes volumes (data loss!) ``` New users do `down -v` casually and lose their dev database. **Using `localhost` to talk between services** ```yaml # WRONG: from inside `api` container, localhost is the api container itself DATABASE_URL: postgres://postgres@localhost:5432/app # RIGHT: use the service name DATABASE_URL: postgres://postgres@db:5432/app ``` Every container has its own loopback. Service-to-service traffic goes by service name, never localhost. **Forgetting `condition: service_healthy` in `depends_on`** ```yaml # WRONG: api starts when db's container starts, NOT when db is ready to accept queries depends_on: [db] # RIGHT: api starts only after db's healthcheck passes depends_on: db: condition: service_healthy ``` The simple list form just orders container starts. The map form with `condition` actually waits for readiness — assuming the dependency has a `healthcheck`. **Bind-mounting `node_modules` on Mac/Windows** ```yaml # Slow on Docker Desktop because of the cross-VM sync volumes: - .:/app # Faster: keep node_modules inside a named volume, away from the bind sync volumes: - .:/app - api_node_modules:/app/node_modules ``` Classic local-dev gotcha that turns 2-second installs into 60-second waits. ### Real-world usage - **Local dev environments:** the dominant use case. `git clone && docker compose up` is the standard onboarding. - **CI/CD test fixtures:** spin up Postgres + Redis + the app's mock dependencies in one step, run integration tests, tear down. - **Single-host production:** small services, side projects, internal tools. Compose + `restart: unless-stopped` + a reverse proxy is a perfectly fine stack for low-traffic apps. - **Demo and tutorial repos:** every tool that wants you to try it locally ships a Compose file. The default expectation now. ### Follow-up questions **Q:** What is the difference between `docker compose up` and `docker compose run`? **A:** `up` starts the service as part of the running stack (long-lived, attached to network, port-mapped). `run` creates a new one-off container based on the same service config — typically used for one-shot tasks (`docker compose run api npm test`). `run` does NOT publish ports by default unless you add `--service-ports`. **Q:** Can I have multiple Compose files for one project? **A:** Yes: `docker compose -f compose.yaml -f compose.dev.yaml up`. Later files override earlier ones. Common pattern: a base `compose.yaml` with prod-shaped config, a `compose.override.yaml` (auto-loaded) or `compose.dev.yaml` with dev tweaks (bind mounts, exposed dev ports, hot reload). **Q:** What is a Compose profile? **A:** A way to mark services as opt-in. `profiles: [debug]` in a service means it only starts when you `docker compose --profile debug up`. Useful for debug containers, integration tests, or optional monitoring sidecars that you do not always want running. **Q:** When is Compose not enough? **A:** Multi-host deployment, automatic failover, rolling updates, autoscaling — Compose is single-host and does none of those. When you need them, move to Docker Swarm (still simpler than K8s) or Kubernetes (the industry default for multi-host). **Q:** (Senior) How would you structure a Compose project for prod, staging, and dev with minimum duplication? **A:** Base `compose.yaml` with all services and shared config (images, volumes, healthchecks). Then `compose.dev.yaml` (bind mounts, exposed dev tooling), `compose.staging.yaml` (different domains, no debug, smaller resource limits), `compose.prod.yaml` (resource caps, restart policies, no exposed dev ports). Use `docker compose -f compose.yaml -f compose.<env>.yaml up`. Anything secret stays out of YAML — use `env_file` pointing at environment-specific dotenv files that are not committed. ## Examples ### Three-service app (web + api + db) ```yaml # compose.yaml services: web: image: nginx:1.27-alpine ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro depends_on: [api] api: build: ./api environment: DATABASE_URL: postgres://postgres:devpass@db:5432/app NODE_ENV: production depends_on: db: condition: service_healthy restart: unless-stopped db: image: postgres:16 environment: POSTGRES_PASSWORD: devpass POSTGRES_DB: app volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 3s retries: 5 volumes: pgdata: ``` ```bash $ docker compose up -d --build # build api image, start everything $ docker compose logs -f api # tail api logs $ docker compose exec api sh # shell into api $ docker compose down # stop, keep volumes ``` ### Dev override pattern ```yaml # compose.override.yaml (auto-loaded if present) services: api: build: target: dev # multi-stage Dockerfile with a 'dev' stage volumes: - ./api/src:/app/src # live source mount for hot reload environment: NODE_ENV: development command: npm run dev # override prod CMD ports: - "9229:9229" # node debug port ``` ```bash $ docker compose up -d # auto-merges compose.yaml + compose.override.yaml ``` Prod-shaped base file plus a dev override gives you the same services with dev affordances on top.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.