普通视图

发现新文章,点击刷新页面。
今天 — 2026年3月18日首页

Docker Compose: Define and Run Multi-Container Apps

Docker Compose is a tool for defining and running multi-container applications. Instead of starting each container separately with docker run , you describe all of your services, networks, and volumes in a single YAML file and bring the entire environment up with one command.

This guide explains how Docker Compose V2 works, walks through a practical example with a SvelteKit development server and PostgreSQL database, and covers the most commonly used Compose directives and commands.

Quick Reference

For a printable quick reference, see the Docker cheatsheet .

Command Description
docker compose up Start all services (foreground)
docker compose up -d Start all services in detached mode
docker compose down Stop and remove containers and networks
docker compose down -v Also remove named volumes
docker compose ps List running services
docker compose logs SERVICE View logs for a service
docker compose logs -f SERVICE Follow logs in real time
docker compose exec SERVICE sh Open a shell in a running container
docker compose stop Stop containers without removing them
docker compose build Build or rebuild images
docker compose pull Pull the latest images

Prerequisites

The examples in this guide require Docker with the Compose plugin installed. Docker Desktop includes Compose by default. On Linux, install the docker-compose-plugin package alongside Docker Engine.

To verify Compose is available, run:

Terminal
docker compose version
output
Docker Compose version v2.x.x

Compose V2 runs as docker compose (with a space), not docker-compose. The old V1 binary is end-of-life and not covered in this guide.

The Compose File

Modern Docker Compose prefers a file named compose.yaml, though docker-compose.yml is still supported for compatibility. In this guide, we will use docker-compose.yml because many readers still recognize that name first.

A Compose file describes your application’s environment using three top-level keys:

  • services — defines each container: its image, ports, volumes, environment variables, and dependencies
  • volumes — declares named volumes that persist data across container restarts
  • networks — defines custom networks for service communication (Compose creates a default network automatically)

Compose V2 does not require a version: field at the top of the file. If you see version: '3' in older guides, that is legacy syntax kept for backward compatibility, not something new Compose files need.

YAML uses indentation to define structure. Use spaces, not tabs.

Setting Up the Example

In this example, we will run a SvelteKit development server alongside a PostgreSQL 16 database using Docker Compose.

Start by creating a new SvelteKit project. The npm create command launches an interactive wizard — select “Skeleton project” when prompted, then choose your preferred options for TypeScript and linting:

Terminal
npm create svelte@latest myapp
cd myapp

If you already have a Node.js project, skip this step and use your project directory instead.

Next, create a docker-compose.yml file in the project root:

docker-compose.ymlyaml
services:
 app:
 image: node:20-alpine
 working_dir: /app
 volumes:
 - .:/app
 - node_modules:/app/node_modules
 ports:
 - "5173:5173"
 environment:
 DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/myapp
 depends_on:
 db:
 condition: service_healthy
 command: sh -c "npm install && npm run dev -- --host"

 db:
 image: postgres:16-alpine
 volumes:
 - postgres_data:/var/lib/postgresql/data
 environment:
 POSTGRES_USER: postgres
 POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
 POSTGRES_DB: myapp
 healthcheck:
 test: ["CMD-SHELL", "pg_isready -U postgres"]
 interval: 5s
 timeout: 5s
 retries: 5

volumes:
 postgres_data:
 node_modules:

Compose V2 automatically reads a .env file in the project directory and substitutes ${VAR} references in the compose file. Create a .env file to define the database password:

.envtxt
POSTGRES_PASSWORD=changeme
Warning
Do not commit .env to version control. Add it to your .gitignore file to keep credentials out of your repository.

The password above is for local development only. We will walk through each directive in the compose file in the next section.

Understanding the Compose File

Let us go through each directive in the compose file.

services

The services key is the core of any compose file. Each entry under services defines one container. The key name (app, db) becomes the service name, and Compose also uses it as the hostname for inter-service communication — so the app container can reach the database at db:5432.

image

The image directive tells Compose which Docker image to pull. Both node:20-alpine and postgres:16-alpine are pulled from Docker Hub if they are not already present on your machine. The alpine variant uses Alpine Linux as a base, which keeps image sizes small.

working_dir

The working_dir directive sets the working directory inside the container. All commands run in /app, which is where we mount the project files.

volumes

The app service uses two volume entries:

txt
- .:/app
- node_modules:/app/node_modules

The first entry is a bind mount. It maps the current directory on your host (.) to /app inside the container. Any file you edit on your host is immediately reflected inside the container, which is what enables hot-reload during development.

The second entry is a named volume. Without it, the bind mount would overwrite /app/node_modules with whatever is in your host directory — which may be empty or incompatible. The node_modules named volume tells Docker to keep a separate copy of node_modules inside the container so the bind mount does not interfere with it.

The db service uses a named volume for its data directory:

txt
- postgres_data:/var/lib/postgresql/data

This ensures that your database data persists across container restarts. When you run docker compose down, the postgres_data volume is kept. Only docker compose down -v removes it.

Named volumes must be declared at the top level under volumes:. Both postgres_data and node_modules are listed there with no additional configuration, which tells Docker to manage them using its default storage driver.

ports

The ports directive maps ports between the host machine and the container in "HOST:CONTAINER" format. The entry "5173:5173" exposes the Vite development server so you can open http://localhost:5173 in your browser.

environment

The environment directive sets environment variables inside the container. The app service receives the DATABASE_URL connection string, which a SvelteKit application can read to connect to the database.

Notice the ${POSTGRES_PASSWORD} syntax. Compose reads this value from the .env file and substitutes it before starting the container. This keeps credentials out of the compose file itself.

depends_on

By default, depends_on only controls the order in which containers start — it does not wait for a service to be ready. Compose V2 supports a long-form syntax with a condition key that changes this behavior:

yaml
depends_on:
 db:
 condition: service_healthy

With condition: service_healthy, Compose waits until the db service passes its healthcheck before starting app. This prevents the Node.js process from trying to connect to PostgreSQL before the database is accepting connections.

command

The command directive overrides the default command defined in the image. Here it runs npm install to install dependencies, then starts the Vite dev server with --host to bind to 0.0.0.0 inside the container (required for Docker to forward the port to your host):

txt
sh -c "npm install && npm run dev -- --host"

Running npm install on every container start is slow but convenient for development — you do not need to install dependencies manually before running docker compose up. For production, use a multi-stage Dockerfile to pre-install dependencies and build the application.

healthcheck

The healthcheck directive defines a command Compose runs inside the container to determine whether the service is ready. The pg_isready utility checks whether PostgreSQL is accepting connections:

yaml
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5

Compose runs this check every 5 seconds. Once the service passes its first check, it is marked as healthy and any services using condition: service_healthy are allowed to start. If the check fails 5 consecutive times, the service is marked as unhealthy.

Managing the Application

Run all commands from the project directory — the directory where your docker-compose.yml is located.

Starting Services

To start all services and stream their logs to your terminal, run:

Terminal
docker compose up

Compose pulls any missing images, creates the network, starts the db container, waits for it to pass its healthcheck, then starts the app container. Press Ctrl+C to stop.

To start in detached mode and run the services in the background:

Terminal
docker compose up -d

Viewing Status and Logs

To list running services and their current state:

Terminal
docker compose ps
output
NAME IMAGE COMMAND SERVICE STATUS PORTS
myapp-app-1 node:20-alpine "docker-entrypoint.s…" app Up 2 minutes 0.0.0.0:5173->5173/tcp
myapp-db-1 postgres:16-alpine "docker-entrypoint.s…" db Up 2 minutes 5432/tcp

To view the logs for a specific service:

Terminal
docker compose logs app

To follow the logs in real time (like tail -f):

Terminal
docker compose logs -f app

Press Ctrl+C to stop following.

Running Commands Inside a Container

To open an interactive shell inside the running app container:

Terminal
docker compose exec app sh

This is useful for running one-off commands, inspecting the file system, or debugging. Type exit to leave the shell.

Stopping and Removing Services

To stop running containers without removing them — preserving named volumes and the network:

Terminal
docker compose stop

To start them again after stopping:

Terminal
docker compose start

To stop containers and remove them along with the network:

Terminal
docker compose down

Named volumes (postgres_data, node_modules) are preserved. Your database data is safe.

To also remove all named volumes — this deletes your database data:

Terminal
docker compose down -v

Use this when you want a completely clean environment.

Common Directives Reference

The following directives are not used in the example above but are commonly needed in real projects.

restart

The restart directive controls what Compose does when a container exits:

yaml
services:
 app:
 restart: unless-stopped

The available policies are:

  • no — do not restart (default)
  • always — always restart, including on system reboot
  • unless-stopped — restart unless the container was explicitly stopped
  • on-failure — restart only when the container exits with a non-zero status

Use unless-stopped for long-running services in single-host deployments.

build

Instead of pulling a pre-built image, build tells Compose to build the image from a local Dockerfile :

yaml
services:
 app:
 build:
 context: .
 dockerfile: Dockerfile

context is the directory Compose sends to the Docker daemon as the build context. dockerfile specifies the Dockerfile path relative to context. If both the image and a build context exist, build takes precedence.

env_file

The env_file directive injects environment variables into a container from a file at runtime:

yaml
services:
 app:
 env_file:
 - .env.local

This is different from Compose’s native .env substitution. The .env file at the project root is read by Compose itself to substitute ${VAR} references in the compose file. The env_file directive injects variables directly into the container’s environment. Both can be used together.

networks

Compose creates a default bridge network connecting all services automatically. You can define custom networks to isolate groups of services or control how containers communicate:

yaml
services:
 app:
 networks:
 - frontend
 - backend
 db:
 networks:
 - backend

networks:
 frontend:
 backend:

A service only communicates with other services on the same network. In this example, app can reach db because both share the backend network. A service attached only to frontend — not backend — has no route to db.

profiles

Profiles let you define optional services that only start when explicitly requested. This is useful for development tools, debuggers, or admin interfaces that you do not want running all the time:

yaml
services:
 app:
 image: node:20-alpine

 adminer:
 image: adminer
 profiles:
 - tools
 ports:
 - "8080:8080"

Running docker compose up starts only app. To also start adminer, pass the profile flag:

Terminal
docker compose --profile tools up

Troubleshooting

Port is already in use
Another process on your host is using the same port. Change the host-side port in the ports: mapping. For example, change "5173:5173" to "5174:5173" to expose the container on port 5174 instead.

Service cannot connect to the database
If you are not using condition: service_healthy, depends_on only ensures the db container starts before app — it does not wait for PostgreSQL to be ready to accept connections. Add a healthcheck to the db service and set condition: service_healthy in depends_on as shown in the example above.

node_modules is empty inside the container
The bind mount .:/app maps your host directory to /app, which overwrites /app/node_modules with whatever is on your host. If your host directory has no node_modules, the container sees none either. Fix this by adding a named volume entry node_modules:/app/node_modules as shown in the example. Docker preserves the named volume’s contents and the bind mount does not overwrite it.

FAQ

What is the difference between docker compose down and docker compose stop?
docker compose stop stops the running containers but leaves them on disk along with the network and volumes. You can restart them with docker compose start. docker compose down removes the containers and the network. Named volumes are kept unless you add the -v flag.

How do I view logs for a specific service?
Run docker compose logs SERVICE, replacing SERVICE with the service name defined in your compose file (for example, docker compose logs app). To follow logs in real time, add the -f flag: docker compose logs -f app.

How do I pass secrets without hardcoding them in the Compose file?
Create a .env file in the project directory with your credentials (for example, POSTGRES_PASSWORD=changeme) and reference them in the compose file using ${VAR} syntax. Compose reads the .env file automatically. Add .env to your .gitignore so credentials are never committed to version control.

Can I use Docker Compose in production?
Compose works well for single-host deployments — it is simpler to operate than Kubernetes when you only have one server. Use restart: unless-stopped to keep services running after reboots, and keep secrets in environment variables rather than hardcoded values. For multi-host deployments, look at Docker Swarm or Kubernetes.

What is the difference between a bind mount and a named volume?
A bind mount (./host-path:/container-path) maps a specific directory from your host into the container. Changes on either side are immediately visible on the other. A named volume (volume-name:/container-path) is managed entirely by Docker — it persists data independently of the host directory structure and is not tied to a specific path on your machine. Use bind mounts for source code (so edits take effect immediately) and named volumes for database data (so it persists reliably).

Conclusion

Docker Compose gives you a straightforward way to define and run multi-container development environments with a single YAML file. Once you are comfortable with the basics, the next step is writing custom Dockerfiles to build your own images instead of relying on generic ones.

❌
❌