Cheat Sheet

Docker Compose Commands

Docker Compose runs a multi-container app from one compose.yaml file. This sheet covers both halves of the job: the docker compose commands you run from the shell, and the YAML keys you write in the file, including the command: key that so many searches for this page are really about.

Last updated August 29, 2026

Compose v2 is a Docker CLI plugin, so the command is with a space. The hyphenated was v1, written in Python, and has been retired; if a tutorial still uses it, swap in the space. The file is by default ( still works), and the old key at the top is obsolete and ignored.

Start and Stop

Run these from the directory that holds , or point at it with .

CommandWhat it does
Create and start every service, attached to the logs (Ctrl+C stops)
Start in the background
Rebuild images that have a section first
Only one service (and what it )
Only one service, without its dependencies
Recreate containers even if nothing changed
Pull newer images before starting
Return only when services are running and healthy
Also remove containers for services no longer in the file
Run three copies of a service
Stop containers, keep them and their networks
Stop one service
Start stopped containers again
Restart every service
Restart one service (does not pick up file changes; use )
Stop and remove containers and networks
Also remove named volumes declared in the file. This deletes data
Also remove the images
Also remove leftover containers from removed services
/ Freeze and thaw
SIGKILL a service ( for another signal)

is also how you apply changes: it compares the file against the running containers and recreates only the ones whose configuration changed. does not do that.

Status and Logs

CommandWhat it does
Containers for this project, with state and ports
Include stopped ones
Just the service names
Every Compose project running on this host
Logs from every service
Follow
Follow one service
Last 100 lines, then follow
Only the last ten minutes
With timestamps
Processes in every container
Live CPU and memory per container
Stream container events for the project
Which image and tag each service runs
Which host port maps to container port 80

Exec and Run

runs inside an existing container. starts a new one from the service definition, which is the right tool for one-off jobs like migrations.

CommandWhat it does
Shell in the running container ( where the image has it)
As root
Run one command
No TTY, for scripts and pipes
With an extra variable
In a specific directory
New throwaway container, with the service's volumes, env and network
Run a command in one
Without starting the services it depends on
Publish the service's ports too ( skips them by default)
Override an environment variable
Ignore the image's entrypoint

Without , every leaves a stopped container behind. shows them and clears them.

Build and Pull

CommandWhat it does
Build every service with a section
Build one
Rebuild every layer
Pull a newer base image first
Pass a build argument
Pull the latest image for every service
Pull one
Keep going if one image is unavailable
Push built images to their registry
Create containers without starting them

Update a running stack to newer images:

docker compose pull
docker compose up -d

recreates only the containers whose image changed. For services you build yourself, does the same thing in one step. The Dockerfile generator is a quick way to get the side started.

Files, Projects and Profiles

CommandWhat it does
Use a specific file
Layer files; later ones override earlier ones
Set the project name (default is the directory name)
Variables for substitution in the file
Also start services tagged
Profiles apply to too, or profiled containers stay behind
Resolve relative paths from another directory
Check what the merged file looks like

is loaded automatically alongside when you do not pass , which is the usual way to keep dev-only ports and bind mounts out of the main file. The same flags can live in the environment: , , .

A file in the project directory is read automatically for substitution inside . It is not the same as on a service, which passes variables into the container.

Validate and Clean Up

CommandWhat it does
Validate the file and print the fully resolved version
Validate only, no output; non-zero exit on error
List service names
List volume names
List profiles
Remove stopped service containers
Stop, remove, and drop anonymous volumes without asking
Remove containers for services you deleted from the file
Which Compose you have

on is a warning, not an error: a service was renamed or removed from the file and its old container is still around. on or clears it.

compose.yaml Keys

The keys you actually use, per service. Indentation is two spaces and matters.

KeyExampleNotes
top levelOne entry per container
Image to pull
or Build from a Dockerfile instead. Add too to name the result
Fixed name; default is . Prevents
. Quote them; binds to localhost only
Reachable by other services only, not the host
Named volume (declare it under top-level )
Bind mount; relative paths are allowed here
Read-only
or Variables in list or map form
Load variables from a file into the container
(default), , ,
Start order only; does not wait for readiness
Wait for the healthcheck to pass
see belowHow Compose decides the service is healthy
Attach to a named network (declare under top-level )
Replace the image's CMD
Replace the image's ENTRYPOINT
Run as a UID and GID
Working directory
Only start with
Metadata; see the Traefik label generator
Cap log size
Memory and CPU caps (honoured by too, not only Swarm)

Top-level and declare the names the services refer to:

volumes:
  data:
networks:
  backend:

Services on the same project network reach each other by service name (), which is why works. Compose creates a default network per project, so you only need when you want to split services up.

Example compose.yaml

A web app built from the local Dockerfile, a Postgres database with a healthcheck, and a named volume for its data:

services:
  web:
    build: .
    image: myapp:latest
    ports:
      - "8080:3000"
    env_file: .env
    environment:
      DB_HOST: db
      NODE_ENV: production
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:16
    volumes:
      - data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    restart: unless-stopped

volumes:
  data:

reads from the shell or the project file and falls back to . The Docker Compose generator builds files like this for common stacks.

Healthcheck and depends_on

alone only orders startup: the container starts first, but Postgres may still be initialising when connects. A healthcheck plus makes Compose wait.

KeyExampleNotes
Exit 0 is healthy. runs directly, runs through
Shell form when you need , pipes or variables
Time between checks (default 30s)
Fail the check if it takes longer
Consecutive failures before
Grace period at startup during which failures do not count
Turn off a healthcheck the image defines
in The default: container started
in Wait until the healthcheck passes
in Wait for a one-shot service (migrations) to exit 0
in Restart this service when the dependency is recreated

The check runs inside the container, so or has to exist in that image. Alpine images often lack ; usually works instead. shows or next to the state, and shows the last few results.

command vs entrypoint

SituationWhat to write
Change the arguments, keep the image's entrypoint
Replace the whole command (image has no entrypoint)
Run a shell script with or pipes
Same, in list form
Ignore the image's entrypoint entirely plus
Keep a container alive with no real process (or )
Interactive shell on or add and
Pass arguments to an entrypoint script; the script gets them as ,

The string form () is split on spaces by Compose without a shell, so quotes inside it are kept literally and is passed as an argument. When in doubt, use the list form and name explicitly. A multi-line script can go under a folded block scalar:

    command: >
      sh -c "npm run migrate &&
             npm run seed &&
             npm start"

Gotchas

  • does not read the file again. After editing , run ; it recreates only what changed.
  • deletes every named volume declared in the file, including the database. alone keeps them.
  • without a only orders container starts. Add a healthcheck and when the app crashes on boot because the database is not ready.
  • Ports in YAML must be quoted (); unquoted is parsed as a base-60 number by some YAML parsers and becomes garbage.
  • in the project directory feeds substitution in the file. feeds the container. They are different files with different jobs, and a variable in does not reach the container unless you also pass it through or .
  • Services started with need the same on , or their containers are left running.
  • on means another container, or a process on the host, holds that port. and find it; see Error response from daemon for the other variants.
  • Bare commands work on Compose containers too: , . The docker commands sheet covers them.

Docker Compose Cheat Sheet FAQ

What is the difference between command and entrypoint in docker compose?
They map straight onto CMD and ENTRYPOINT in the Dockerfile. entrypoint: is the executable that always runs; command: is the arguments handed to it, or the whole command line when there is no entrypoint. Setting command: in compose replaces the image's CMD but leaves its ENTRYPOINT alone, so for an image whose entrypoint is a script, your command: becomes that script's arguments. Setting entrypoint: replaces the image's ENTRYPOINT and also discards its CMD, so give a command: too if you need one. To run a plain shell in an image that has a strict entrypoint, set entrypoint: ["sh", "-c"] and put the script in command:.
Why does docker compose say command not found?
Two different errors share that wording. docker: 'compose' is not a docker command means the Compose plugin is missing: install docker-compose-plugin from Docker's apt or dnf repository (Docker Desktop includes it), and check with docker compose version. docker-compose: command not found means you typed the old hyphenated v1 binary, which has been retired; use docker compose with a space, or add a shell alias if scripts depend on the old name. If the error comes from inside a container, such as /bin/sh: npm: not found, the command: you wrote is not installed in that image.
How do I run a command after the container starts?
Compose has no post-start hook, so you have three options. For one-off setup, run it from the outside after up: docker compose exec web ./migrate.sh, or docker compose run --rm web ./seed.sh as a separate container that shares the same volumes and network. For something that must happen on every start, wrap it in the command: itself, for example command: sh -c "./migrate.sh && node server.js". For something that must wait for another service, put a healthcheck on that service and use depends_on with condition: service_healthy, which is what most 'run after start' questions are really about.
How do I keep a container running in docker compose?
A container stops when its main process exits, so an image with no long-running process (a bare ubuntu or alpine, a build toolchain) exits right after up. Give it something to wait on: command: sleep infinity or command: tail -f /dev/null keeps it alive so you can docker compose exec into it. Add tty: true and stdin_open: true if you want an interactive shell to attach. This is a development trick; a service that needs to stay up in production should run its real process in the foreground.
How do I write a multi-line command in compose?
Use the list form and put each word on its own line: command: followed by - sh, - -c, - "npm run build && npm start". The list form passes each element as one argument with no shell parsing, which is why the shell and its -c flag have to be listed explicitly when you want && or pipes. For a long script, a YAML block scalar works: command: > then the script indented underneath, which folds the lines into one string, and wrap the whole thing in sh -c so the shell sees it as one script.

Related cheat sheets