Where to start First steps track · 15 stages
Linux Labs

docker compose — a container stack in one file

14 min Docker Compose 2.40 · Docker 29.1 · Ubuntu 24.04

docker compose describes a container stack with network, volumes and variables in compose.yaml and starts it at once.

Cheat sheet

8 commands Printable version
  • sudo docker compose up -d start all of compose.yaml
  • sudo docker compose ps project containers and health
  • sudo docker compose logs -f backend one service's logs
  • sudo docker compose exec proxy sh a shell in a service
  • sudo docker compose config -q validate the file
  • sudo docker compose restart proxy restart a service
  • sudo docker compose down remove containers and network
  • sudo docker compose down -v … and volumes with their data

Overview

docker compose is the Docker plugin for a stack of several containers. Services, their images, ports, volumes, variables and start order live in one compose.yaml file. docker compose up -d creates the network, volumes and containers from it; down removes them. Everything compose does can be done by hand with docker commands — the file saves you long docker run lines and keeping the start order in your head.

The lab machine has Docker Compose 2.40, written with a space: docker compose. The old hyphenated docker-compose program is not installed. Like other Docker commands, compose goes through sudo.

Compose looks for compose.yaml (or docker-compose.yml) in the current directory. Another file — -f stack.yaml.

The compose.yaml file

A stack of two services: backend serves a page from a volume, proxy is nginx in front of it and the only one with a port to the outside.

yaml
services:
  backend:
    image: busybox
    command:
      - sh
      - -c
      - >-
        echo "<h1>$$GREETING</h1>" > /www/index.html &&
        exec httpd -f -p 8000 -h /www
    environment:
      GREETING: ${GREETING:-hello}
    volumes:
      - data:/www
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null",
             "http://127.0.0.1:8000/"]
      interval: 5s
      timeout: 3s
      retries: 3
  proxy:
    image: nginx:alpine
    ports:
      - "8090:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      backend:
        condition: service_healthy
    restart: unless-stopped
volumes:
  data:

Next to it lies nginx.conf with proxy_pass http://backend:8000; — nginx reaches its neighbour by service name.

Service key What it sets Same in docker run
image the image image name
build a directory with a Dockerfile — build the image docker build
command a command instead of the image CMD words after the image
environment environment variables -e
ports "host:container" -p
volumes volume:/path or ./dir:/path -v
depends_on what to start first —
healthcheck the healthcheck --health-cmd
restart the restart policy --restart

The top-level volumes key declares named volumes, networks extra networks. The syntax is YAML: indent with spaces, tabs are not allowed. Quote ports: "8090:80".

A volume created beforehand (docker volume create keep) is attached as external — compose neither creates nor removes it:

yaml
volumes:
  data:
    external: true
    name: keep

If the external volume is missing, up fails: external volume "keep" not found. Without external: true compose creates its own empty <project>_data volume, even if keep with data sits right there.

Without a networks key all project services share one network. Your own networks are declared under top-level networks: and listed per service: networks: [front, back]. A service name is visible only on shared networks: web on front cannot find db on back (server can't find db), while app, attached to both, sees both.

Project: names of containers, network and volumes

The file describes a project. The project name is the name of the directory holding compose.yaml; everything compose creates is named after it:

text
student@ubuntu:~/stack$ sudo docker compose up -d
 Network stack_default  Created
 Volume stack_data  Created
 Container stack-backend-1  Created
 Container stack-proxy-1  Created
 Container stack-backend-1  Started
 Container stack-backend-1  Waiting
 Container stack-backend-1  Healthy
 Container stack-proxy-1  Started
What Name
network <project>_default — stack_default
volume data <project>_data — stack_data
service container <project>-<service>-1 — stack-backend-1

All project services share one network, and each is visible to the others by its service name:

text
student@ubuntu:~/stack$ sudo docker compose exec proxy nslookup backend
Server:         127.0.0.11
…
Name:   backend
Address: 172.18.0.2

Compose commands address services by their names from the file: logs backend, exec proxy, restart proxy. Run them from the project directory — or with -f and -p.

Variables

Compose substitutes ${GREETING:-hello} itself, before any container starts: the environment variable's value, or hello if it is unset. Variables come from a .env file next to compose.yaml:

text
student@ubuntu:~/stack$ echo GREETING=privet > .env
student@ubuntu:~/stack$ sudo docker compose up -d
 Container stack-backend-1  Recreate
 Container stack-backend-1  Recreated
 Container stack-proxy-1  Running
…
student@ubuntu:~/stack$ curl -s localhost:8090/
<h1>privet</h1>

up -d re-created only backend: its environment changed. proxy did not change and kept running.

A shell variable in front of sudo never reaches compose: sudo clears the environment, so GREETING=privet sudo docker compose up -d changes nothing. Put the value in .env.

$$ is a literal dollar sign: compose leaves $$GREETING in command alone, and the shell inside the container expands it. docker compose config shows the result of substitution.

Start order and healthcheck

depends_on: [backend] only sets the order: proxy starts once the backend container is running, whether it is ready to answer or not. condition: service_healthy waits for readiness — compose waits for the healthy status from the backend healthcheck.

healthcheck.test is a command inside the container: code 0 means healthy. Docker runs it every interval, waits at most timeout, and after retries failures in a row marks it unhealthy. The status shows in ps:

text
student@ubuntu:~/stack$ sudo docker compose ps
NAME              …   SERVICE   …   STATUS
stack-backend-1   …   backend   …   Up 6 seconds (healthy)
stack-proxy-1     …   proxy     …   Up Less than a second

The PORTS column of proxy shows 0.0.0.0:8090->80/tcp, [::]:8090->80/tcp.

The check command must exist in the image: busybox and alpine have wget but no curl.

Stack commands

Command What it does
up -d creates what is missing and re-creates services whose description changed (Recreate), leaves the rest alone (Running)
restart backend restarts the same container: environment and options stay the same
stop, start stops and starts without removing containers
ps container status: Up … (healthy), Restarting (1) …, Exited (137)
logs backend a service's output; without a name, all services
down removes the project's containers and network; named volumes stay
down -v the same plus the project's volumes

Only up -d applies a compose.yaml or .env edit. restart restarts the process in the old container: it re-reads files from the host, but the variables stay as they were.

text
student@ubuntu:~/nets$ echo GREETING=privet > .env
student@ubuntu:~/nets$ sudo docker compose restart app
student@ubuntu:~/nets$ sudo docker compose exec app env | grep GREETING
GREETING=hello
student@ubuntu:~/nets$ sudo docker compose up -d
 Container nets-app-1  Recreate
…
student@ubuntu:~/nets$ sudo docker compose exec app env | grep GREETING
GREETING=privet

Options

20

The main subcommands and options are in the table below. The full list is docker compose --help and docker compose <command> --help.

Option What it does Example
up Create and start everything in compose.yaml; changed services are re-created, the rest are left alone. docker compose up -d
-d --detach Start in the background and return the prompt; without it up holds the terminal and shows logs. docker compose up -d
ps Project containers — status, health, ports; with -a, stopped ones too. docker compose ps -a
logs Service output prefixed with the service name; -f follows, --tail shows the last lines. docker compose logs -f --tail 20 backend
exec A command in the running container of a service, named as in compose.yaml. docker compose exec proxy nslookup backend
config Validate the file and print the final configuration with variables substituted. docker compose config
down Stop and remove the project's containers and network; named volumes stay. docker compose down
--build Rebuild images of services that have a build key before starting. docker compose up -d --build
--wait Wait until services are running or healthy; if not, exit with a non-zero code. docker compose up -d --wait
--format json One JSON object per line for each container — for scripts and jq. docker compose ps --format json
-q --quiet Validation only — no output, exit code 0 if the file is valid. docker compose config -q
restart Restart a service's containers without re-creating them; compose.yaml edits are not applied this way. docker compose restart proxy
stop Stop services without removing containers; bring back with start or up -d. docker compose stop backend
-v --volumes Remove the project's volumes too — their data is lost. dangerous docker compose down -v
build Build images for services that have a build key. docker compose build
-f --file FILE Use another file instead of compose.yaml in the current directory. docker compose -f stack.yaml up -d
-T No terminal — for scripts and pipelines. docker compose exec -T backend cat /etc/hosts
--services Only the service names, one per line. docker compose config --services
-p --project-name NAME Project name instead of the directory name — the prefix of containers, network and volumes. docker compose -p demo up -d
--env-file FILE A variables file for substitution instead of .env next to compose.yaml. docker compose --env-file prod.env up -d

Nothing found. Try a shorter query.

Recipes

4

Validate the file before starting

bash
sudo docker compose config -q && echo OK   # validation only
sudo docker compose config                 # result with variables
sudo docker compose config --services      # service names

Stack state for a script

bash
sudo docker compose ps --format json \
  | jq -r '"\(.Service) \(.State) \(.Health)"'

--format json prints one JSON object per line for each container — jq parses them one by one.

Stop one service

bash
sudo docker compose stop backend       # the container stays
sudo docker compose ps -a              # backend is Exited
sudo docker compose up -d              # bring it back

Logs of all services at once

text
student@ubuntu:~/stack$ sudo docker compose logs --tail 2
proxy-1  | 2026/10/20 14:39:21 [notice] 1#1: start worker process 21
proxy-1  | 172.18.0.1 - - [20/Oct/2026:14:39:22 +0000] "GET / HTTP/1.1"…

Each line starts with the service name. One service — logs backend; follow — logs -f.

Troubleshooting

11

no configuration file provided: not found

The current directory has no compose.yaml. Go to the project directory or name the file: docker compose -f /srv/stack/compose.yaml ps.

additional properties '…' not allowed

text
validating /home/student/stack/compose.yaml: services.backend
additional properties 'enviroment' not allowed

A misspelled key or a key at the wrong indent level. The path to the error is services.backend; compare the key name with the table above.

yaml: line 3: found character that cannot start any token

Line 3 has a tab or another character YAML cannot start a value with. YAML indents with spaces only. The site's "YAML ⇄ JSON" tool in the Tools section also points to the error.

service "backend" refers to undefined volume

The service mounts a named volume missing from the top-level volumes:. Declare it there or write a bind mount path starting with ./ or /.

external volume "…" not found

A volume with external: true must exist before up; by then compose has already created the project network. Compare the name: with docker volume ls. If the volume is missing, create it (docker volume create keep) or restore the data from a backup.

dependency failed to start: container … is unhealthy

backend failed its healthcheck, and proxy with condition: service_healthy stayed Created. See what the check returns: docker compose ps -a (status unhealthy) and docker inspect -f '{{json .State.Health}}' <container> — it holds the output of the last attempts. Common causes: the wrong port in test, no wget in the image, a service that starts slower than interval × retries.

The "X" variable is not set

The "MODE" variable is not set. Defaulting to a blank string. — the variable is neither in the environment nor in .env, so compose put an empty string in. Set it in .env or give a default: ${MODE:-prod}.

the attribute version is obsolete

A version: "3.8" line at the top of the file is obsolete and compose ignores it. Delete it and the warning goes away.

A service in a restart loop

docker compose ps shows Restarting: the main process crashes and restart: brings it up again. The cause is in docker compose logs <service>: a wrong variable, a missing file, a busy port. Fix compose.yaml or .env and run up -d.

Pulling … failed to resolve reference

text
 backend Pulling
 backend Error failed to resolve reference "docker.io/library/busybox…

The service image is not on the machine, so compose went to the registry for it. Without network, load the image from a file (docker load -i) and check the name and tag in image: against docker images.

service "proxy" is not running

exec works only in a running service. docker compose ps -a shows what happened to it; docker compose up -d brings it up.

Danger zone

2

docker compose down -v

down removes containers and the network and keeps volumes. -v removes the project volumes too: the database, uploads, everything the services kept in volumes: is gone for good. External volumes (external: true) are left alone by -v.

Another project name means other volumes

A volume name is built from the project name: stack_data. Copy compose.yaml to another directory or run it with -p, and compose creates a new empty volume <new name>_data — the service starts with no data. The old data is safe in the old volume: docker volume ls.

Terms

2
compose service
An entry under services in compose.yaml: the image, command, ports and volumes of a container. The service name is its DNS name on the project network.
healthcheck
A command Docker runs inside a container at intervals: exit code 0 means healthy, several failures in a row mean unhealthy. The status shows in docker ps. Also appears in: Docker

See also

  • docker — containers, volumes, networks and inspect without compose.
  • container-images — where service images come from.
  • dockerfile — building your own image for build:.
  • env-vars — environment variables in services.
  • nginx — a proxy in front of the stack's services.
  • curl — checking the stack's answer from the host.
  • jq — parsing ps --format json.
Loading…