docker compose up and down

docker compose up starts the services in a Compose project, and docker compose down tears that project back down. These two commands are the daily start and stop controls for multi-container Docker applications. Knowing exactly what they create, reuse, stop, and delete is what keeps local development fast and prevents accidental data loss.

Overview: How it works

A Compose file describes an application as services, networks, volumes, build instructions, published ports, and environment. A service is usually one container template: for example, a web app service and a database service. When you run docker compose up, the Compose plugin reads compose.yml or docker-compose.yml, calculates the project name, creates the project network and named volumes if needed, builds or pulls images, then creates and starts containers for the services.

Compose is not a separate container runtime. It talks to the same Docker Engine API used by docker run. The Engine still manages images as read-only layered templates, and each container still gets a thin writable layer plus a running process. Compose adds project-level coordination: containers receive labels that record the project and service they belong to, and they join a default bridge network where service names become DNS names. That is why a container named by the api service can connect to a database using host name db.

By default, the project name comes from the directory name. This matters because Compose names resources with that project prefix. A directory called shop might produce containers like shop-api-1, a network like shop_default, and a volume like shop_pgdata. You can override the project name with -p or the COMPOSE_PROJECT_NAME environment variable when you need two copies of the same stack running side by side.

docker compose down reverses the project-level work. It stops running service containers, removes those containers, and removes networks created by Compose. It does not remove named volumes by default, because volumes often contain important persistent data. It also does not remove images by default. This difference is intentional: stopping the application should not silently erase the database or force every image to be rebuilt.

Syntax

docker compose [OPTIONS] up [OPTIONS] [SERVICE...]
docker compose [OPTIONS] down [OPTIONS]

The first [OPTIONS] position contains global Compose options such as -f for an alternate file and -p for a project name. The second options position belongs to the specific command.

Option Command Meaning
-f FILE global Use a specific Compose file instead of the default discovery names.
-p NAME global Set the Compose project name, which affects container, network, and volume names.
-d up Start containers in detached mode and return the terminal prompt.
--build up Build service images before starting containers.
--force-recreate up Recreate containers even if their configuration and image have not changed.
--remove-orphans up or down Remove containers from the same project that are no longer defined in the current file.
--volumes down Also remove named volumes declared by the Compose file and anonymous volumes attached to containers.
--rmi local down Remove images that do not have a custom tag.

Examples

Start a simple web service

Create this compose.yml in an empty directory:

services:
  web:
    image: nginx:1.27-alpine
    ports:
      - "8080:80"

Then start it in the foreground:

docker compose up

Output:

[+] Running 2/2
 ✔ Network demo_default  Created
 ✔ Container demo-web-1  Created
Attaching to web-1
web-1  | /docker-entrypoint.sh: Configuration complete; ready for start up

Compose pulled the pinned Nginx image if it was missing, created a project network, created the container, published host port 8080 to container port 80, and attached your terminal to the logs. Pressing Ctrl+C stops the containers for this foreground session, but it does not remove the created container or network. Run docker compose down when you want the project resources removed.

Run a web app and database in the background

A more realistic development stack often has a stateless app and a stateful database:

services:
  api:
    image: node:20-alpine
    working_dir: /app
    command: ["node", "server.js"]
    volumes:
      - ./app:/app
    ports:
      - "3000:3000"
    depends_on:
      - db
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_PASSWORD: changeme
      POSTGRES_DB: appdb
    volumes:
      - pgdata:/var/lib/postgresql/data
volumes:
  pgdata:
docker compose up -d
docker compose ps

Output:

[+] Running 4/4
 ✔ Network api_default     Created
 ✔ Volume api_pgdata       Created
 ✔ Container api-db-1      Started
 ✔ Container api-api-1     Started
NAME        IMAGE                COMMAND                  SERVICE   STATUS          PORTS
api-api-1   node:20-alpine       "docker-entrypoint.s…"   api       Up 3 seconds    0.0.0.0:3000->3000/tcp
api-db-1    postgres:16-alpine   "docker-entrypoint.s…"   db        Up 4 seconds    5432/tcp

The -d flag starts the project in detached mode, so logs keep running in Docker while your shell returns. The api service can reach PostgreSQL at host name db on the internal Compose network. The database stores files in the named volume pgdata, which Docker prefixes with the project name. The bind mount ./app:/app maps your source directory into the Node container for local development.

Stop and remove the project

docker compose down

Output:

[+] Running 3/3
 ✔ Container api-api-1  Removed
 ✔ Container api-db-1   Removed
 ✔ Network api_default  Removed

This removes the service containers and Compose-created network. It intentionally leaves api_pgdata in place. If you later run docker compose up -d again in the same project, PostgreSQL will reuse the existing volume and the database contents will still be there.

Reset everything, including volumes

docker compose down --volumes --remove-orphans

Output:

[+] Running 4/4
 ✔ Container api-api-1  Removed
 ✔ Container api-db-1   Removed
 ✔ Volume api_pgdata    Removed
 ✔ Network api_default  Removed

Use this when you want a clean local reset, such as rebuilding a seed database from scratch. Do not use --volumes casually against a project that contains data you care about. It removes named volumes declared in the Compose file for that project.

How it works step by step

  1. Compose loads and merges configuration from the selected Compose file or files. It resolves variables, service definitions, networks, volumes, and profiles.
  2. It chooses a project name. This name is used in Docker labels and generated resource names, allowing Compose to find the same project later.
  3. For each service, Compose checks whether an image must be built, pulled, or reused. If a service has build:, docker compose up --build asks Docker to build the image before starting.
  4. Compose creates missing named volumes and networks. The default network gives services automatic DNS entries matching their service names.
  5. Compose creates or reuses containers. If the service configuration changed, Compose recreates the affected container so the new settings take effect.
  6. Compose starts containers in dependency order as much as possible. depends_on controls start order, but it does not prove that a database is ready to accept connections unless you add health checks and application retry logic.
  7. With foreground up, Compose attaches log streams to your terminal. With up -d, containers keep running in the background.
  8. down finds containers and networks with the project labels, stops the containers, removes them, and removes project networks. Volumes and images stay unless you request extra cleanup.

Common Mistakes

Expecting Ctrl+C to be the same as down

docker compose up
# Press Ctrl+C and assume everything was deleted

This stops a foreground run, but it is not a full cleanup. Containers may remain in an exited state, and networks can remain. The fix is to run:

docker compose down

Deleting database data by using –volumes automatically

docker compose down --volumes

This is useful for a reset, but wrong as a default shutdown command for a stateful stack. It removes project volumes such as PostgreSQL data directories. Use plain docker compose down for normal cleanup, and reserve --volumes for deliberate resets.

Changing project names without realizing volumes change too

docker compose -p api_a up -d
docker compose -p api_b up -d

These are two different projects. They get different containers, networks, and project-prefixed volumes. That is useful for parallel environments, but it surprises people who expect the second command to reuse the first project database. Use a stable project name when you need stable resource names.

Assuming depends_on waits for readiness

depends_on starts one service before another, but a just-started database may still be initializing. Production-grade apps should retry connections, and development stacks can add health checks when startup order needs to be more explicit.

Best Practices

  • Use docker compose up -d for normal background development, then inspect with docker compose ps and docker compose logs.
  • Use plain docker compose down for routine cleanup so named volumes survive.
  • Use docker compose down --volumes only when you intentionally want to delete project data.
  • Pin image tags such as postgres:16-alpine instead of using latest, because latest moves over time and hurts reproducibility.
  • Set a stable project name with -p or COMPOSE_PROJECT_NAME in scripts and CI jobs.
  • Keep state in named volumes and source code in bind mounts. Volumes are Docker-managed storage; bind mounts point to a specific host path.
  • Run docker compose up --build after changing a Dockerfile or build context when you want to force a fresh image build before start.
  • Use --remove-orphans after deleting or renaming services so old containers from the same project do not keep running unnoticed.

Practice Exercises

  1. Create a Compose file with nginx:1.27-alpine published on host port 8088. Start it with up -d, list it with ps, then remove it with down. Hint: the service only needs image and ports.
  2. Add a Redis service using redis:7-alpine and a named volume mounted at /data. Start it, bring it down without --volumes, then start it again. Expected end state: the same named volume still exists.
  3. Run the same Compose file twice with two different project names. Compare the generated container and network names. Hint: use docker compose -p first up -d and a second project name in another command.

Summary

  • docker compose up creates or reuses the containers, networks, volumes, and images needed by a Compose project.
  • docker compose up -d is the standard way to run a stack in the background.
  • docker compose down removes service containers and Compose networks, but keeps named volumes and images by default.
  • docker compose down --volumes deletes project volumes and should be treated as a data reset.
  • Project names matter because Compose uses them to label and name resources.
  • Compose coordinates Docker Engine resources; containers still use normal Docker images, writable layers, networks, and volumes underneath.