Ctrl + K
Docker12 min read

Docker Compose Explained

Understand Docker Compose files, services, networking, volumes, environment variables and how to manage multi-container applications with Docker Compose.

Published: 2026-09-02

Docker Compose is a tool for defining and running multi-container applications. Instead of starting each container manually with separate Docker commands, developers can describe the application's services, networks, volumes and configuration in a single Compose file and manage the entire environment as one application.

Docker Compose is especially useful during local development, testing and small deployments where an application depends on multiple services such as a web server, database, cache or message broker. A Compose file makes these dependencies explicit and provides a repeatable way to start and stop the complete environment.

What Is Docker Compose?

Docker Compose uses a YAML configuration file to describe containers and the resources they need. Each container is represented as a service, and Compose uses the configuration to create the required containers, networks and volumes.

A typical application might contain a frontend, backend API and PostgreSQL database. With Docker Compose, all three services can be described in one file and started together instead of requiring several independent docker run commands.

Why Use Docker Compose?

  • Define multiple containers in one configuration file.
  • Start and stop an entire application with simple commands.
  • Create reproducible development environments.
  • Configure container networking automatically.
  • Persist data with named volumes.
  • Share application configuration between team members.
  • Simplify local development and integration testing.

Docker Compose File

A Compose application is normally described in a compose.yaml or docker-compose.yml file. The file uses YAML syntax, which makes the configuration readable while still allowing developers to describe relatively complex multi-container environments.

services:

app:
image: node:22
ports:
- "3000:3000"

database:
image: postgres:17

Understanding Services

A service represents one type of container that belongs to the application. A service can use an existing image from a registry or build an image from a Dockerfile. Compose creates containers from these service definitions and manages their configuration.

ServiceTypical Purpose
appApplication or API server
databaseDatabase server
redisCache or temporary data store
workerBackground job processor
proxyReverse proxy or web server

Image vs Build

A service can either use an existing Docker image or build its own image from a Dockerfile. The image option is convenient when the required image already exists, while build is useful when the application needs a custom image containing its own source code and dependencies.

services:

app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"

database:
image: postgres:17

Port Mapping

Containers have their own network interfaces, so ports inside a container are not automatically exposed to the host machine. The ports property maps a host port to a container port, allowing applications running inside containers to be accessed from the host.

services:

app:
image: nginx:alpine
ports:
- "8080:80"

In this example, port 8080 on the host is forwarded to port 80 inside the container. Opening http://localhost:8080 therefore reaches the Nginx service running inside the container.

Service Dependencies

Applications frequently depend on other services. For example, an API may require a database to be available before it can perform database operations. Compose allows dependencies to be declared with depends_on.

services:

app:
build: .
depends_on:
- database

database:
image: postgres:17
⚠️ depends_on expresses a startup dependency, but it does not automatically mean that the dependent service is ready to accept connections. Applications should still handle connection retries or use appropriate health checks.

Environment Variables

Environment variables allow configuration to be separated from the Compose file itself. They are commonly used for database credentials, ports, application modes and connection strings.

services:

app:
image: my-app:latest
environment:
NODE_ENV: production
DATABASE_HOST: database
DATABASE_PORT: 5432

Using an Environment File

Compose can load variables from an environment file, which is useful when configuration contains values that should not be written directly into the Compose file. A common approach is to keep a local .env file for development and provide production configuration through the deployment environment.

services:

database:
image: postgres:17
env_file:
- .env
💡 Keep secrets out of files that are committed to source control. Use environment variables, secret-management systems or deployment-specific configuration for sensitive values.

Docker Compose Networking

Compose creates a network for the application's services by default. Services connected to the same Compose network can communicate with each other using their service names as hostnames.

app
|
| database:5432
v
database

For example, an application service can connect to a PostgreSQL service using database as the hostname instead of localhost. This distinction is important because localhost inside a container refers to that container itself, not another service.

Custom Networks

Applications can define custom networks when services need more explicit network separation or when several groups of containers require different communication boundaries.

services:

app:
image: my-app
networks:
- frontend
- backend

database:
image: postgres:17
networks:
- backend

networks:
frontend:
backend:

Volumes and Persistent Data

Containers are designed to be replaceable, so data stored only inside a container can disappear when the container is removed. Volumes provide persistent storage that exists independently of the container lifecycle.

services:

database:
image: postgres:17
volumes:
- postgres_data:/var/lib/postgresql/data

volumes:
postgres_data:

In this example, PostgreSQL stores its database files in the named postgres_data volume. Recreating the database container does not remove the volume, so the database data can survive container replacement.

Bind Mounts

Bind mounts connect a directory or file on the host machine directly to a path inside a container. They are particularly useful during development because source code can be edited on the host while the running container sees the changes.

services:

app:
build: .
volumes:
- ./src:/app/src
Storage TypeTypical Use
Named volumePersistent application data
Bind mountSource code and development files
Temporary filesystemData that does not need persistence

Restart Policies

Compose can configure how a service should behave when its container stops. Restart policies are useful for applications that should automatically recover after failures or host restarts.

services:

app:
image: my-app:latest
restart: unless-stopped

Health Checks

A health check allows Docker to determine whether a container's application is actually responding correctly. This is different from simply checking whether the container process is running.

services:

app:
image: my-app:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
💡 Health checks are especially useful for databases, APIs and other services that may need several seconds to become ready after their containers start.

Building a Multi-Container Application

A common development environment contains an application server and a database. Compose allows both services to be defined together while keeping their responsibilities separate.

services:

app:
build: .
ports:
- "3000:3000"
environment:
DATABASE_HOST: database
depends_on:
- database

database:
image: postgres:17
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD: example
volumes:
- postgres_data:/var/lib/postgresql/data

volumes:
postgres_data:

The application and database now belong to the same Compose project. The app can reach the database through the database service name, while PostgreSQL data remains persistent in the named volume.

Starting the Application

The docker compose up command creates and starts the services described in the Compose file. Adding the -d option runs the containers in the background so the terminal remains available for other commands.

docker compose up

docker compose up -d

Stopping the Application

The docker compose down command stops and removes the containers and networks created for the Compose project. Named volumes are normally preserved unless the appropriate volume-removal option is explicitly used.

docker compose down
⚠️ Be careful when using docker compose down with volume removal options. Removing volumes can permanently delete persistent application data such as database contents.

Useful Docker Compose Commands

CommandPurpose
docker compose upCreate and start services
docker compose up -dStart services in the background
docker compose downStop and remove project containers
docker compose psShow Compose service status
docker compose logsView service logs
docker compose execRun a command inside a service container
docker compose buildBuild service images
docker compose pullDownload newer service images
docker compose restartRestart services

Viewing Logs

Logs are one of the most useful tools for diagnosing problems in a multi-container environment. Compose can display logs from all services or from a specific service.

docker compose logs

docker compose logs app

docker compose logs -f app

The -f option follows new log output as it is produced, making it useful when debugging a running application.

Running Commands Inside Containers

The docker compose exec command runs a command inside an already running service container. It is commonly used for debugging, database administration and inspecting application files.

docker compose exec app sh

docker compose exec database psql -U app -d app

Compose Profiles

Profiles allow optional services to be enabled only when needed. This is useful when a development environment contains tools such as administration dashboards, debugging services or monitoring components that should not always run.

services:

app:
image: my-app

adminer:
image: adminer
profiles:
- tools
docker compose --profile tools up

Development vs Production

Docker Compose is commonly associated with local development, but Compose configurations can also be used in other environments. Production deployments require additional consideration for secrets, persistent storage, networking, backups, monitoring, resource limits and high availability.

DevelopmentProduction
Bind mountsUsually minimized
Local .env filesManaged deployment configuration
Debug toolsOften enabled
Simple networkingMore controlled network design
Local volumesManaged persistent storage and backups

Compose File Organization

Large Compose files become easier to maintain when services are named consistently and related configuration is grouped together. Environment variables, volumes and networks should be defined clearly rather than duplicated across many services.

  • Use descriptive service names.
  • Keep related settings together.
  • Avoid unnecessary duplication.
  • Use environment variables for configurable values.
  • Use named volumes for persistent data.
  • Document unusual configuration choices.

Common Docker Compose Mistakes

Many Compose problems come from misunderstanding container networking, storage or service lifecycle. A configuration may start successfully while still being unreliable because services are not actually ready, data is not persisted or sensitive values are exposed.

  • Using localhost to connect from one container to another.
  • Assuming depends_on guarantees service readiness.
  • Storing important database data without a persistent volume.
  • Hardcoding secrets directly into the Compose file.
  • Publishing internal service ports unnecessarily.
  • Removing volumes without checking whether they contain important data.
  • Using overly broad bind mounts.
  • Treating development configuration as production-ready infrastructure.

Best Practices

  • Keep Compose files readable and logically organized.
  • Use service names for communication between containers.
  • Persist important data with named volumes or appropriate external storage.
  • Use health checks for services that require readiness detection.
  • Keep secrets outside committed Compose files.
  • Expose only the ports that need host access.
  • Use explicit image tags instead of relying on ambiguous defaults.
  • Keep development and production configuration appropriate to their environments.
  • Use profiles for optional development services.
  • Review logs and container health when troubleshooting.
💡 Treat your Compose file as infrastructure configuration. Keeping it deterministic, readable and version-controlled makes development environments much easier to reproduce.
⚠️ A working Compose file is not automatically a secure production deployment. Review secrets, exposed ports, persistent storage, backups, resource usage and network access before using a Compose configuration in production.

Frequently Asked Questions

What is Docker Compose used for?

Docker Compose is used to define and manage applications that consist of multiple containers. It can configure services, networks, volumes, environment variables and other container settings in one YAML file.

What is a Compose service?

A service is a definition for a type of container within a Compose application. Examples include an API server, database, cache or background worker.

What is the difference between Docker and Docker Compose?

Docker provides the container runtime and commands for building and running containers, while Docker Compose provides a declarative way to define and manage multiple related containers as one application.

Can Docker Compose use a Dockerfile?

Yes. A Compose service can use the build property to build its image from a Dockerfile instead of using an existing image.

How do containers communicate in Docker Compose?

Services on the same Compose network can normally communicate using their service names as DNS hostnames. For example, an application can connect to a database service using database as the hostname.

Does Docker Compose preserve database data?

Only if the database data is stored in persistent storage such as a named volume or an external storage system. Removing a container does not necessarily remove a named volume.

Helpful Docker Tools

A Docker Compose Generator helps create Compose configurations for multi-container applications, a Docker Compose Formatter formats and organizes Compose YAML files, a Dockerfile Generator creates Dockerfiles for application containers, an Environment Variable Generator helps prepare application configuration values, and a dotenv Parser helps inspect and parse .env files used by development environments.

Conclusion

Docker Compose provides a practical way to define and manage multi-container applications through a single declarative configuration. Services, networks, volumes and environment variables can be combined to create reproducible development and testing environments without manually managing every container. By understanding service dependencies, container networking, persistent storage, health checks and Compose commands, developers can build environments that are easier to start, debug and maintain. Following good configuration and security practices also makes Compose files more reliable as applications grow.

Found an issue?

Found an error, outdated information, or something missing from this article? Let me know through the Contact page.

Your feedback helps improve our articles and keep them accurate and useful.