Docker Compose Explained
Understand Docker Compose files, services, networking, volumes, environment variables and how to manage multi-container applications with Docker Compose.
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:17Understanding 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.
| Service | Typical Purpose |
|---|---|
| app | Application or API server |
| database | Database server |
| redis | Cache or temporary data store |
| worker | Background job processor |
| proxy | Reverse 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:17Port 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:17Environment 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: 5432Using 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:
- .envDocker 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
databaseFor 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 Type | Typical Use |
|---|---|
| Named volume | Persistent application data |
| Bind mount | Source code and development files |
| Temporary filesystem | Data 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-stoppedHealth 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: 3Building 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 -dStopping 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 downUseful Docker Compose Commands
| Command | Purpose |
|---|---|
| docker compose up | Create and start services |
| docker compose up -d | Start services in the background |
| docker compose down | Stop and remove project containers |
| docker compose ps | Show Compose service status |
| docker compose logs | View service logs |
| docker compose exec | Run a command inside a service container |
| docker compose build | Build service images |
| docker compose pull | Download newer service images |
| docker compose restart | Restart 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 appThe -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 appCompose 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:
- toolsdocker compose --profile tools upDevelopment 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.
| Development | Production |
|---|---|
| Bind mounts | Usually minimized |
| Local .env files | Managed deployment configuration |
| Debug tools | Often enabled |
| Simple networking | More controlled network design |
| Local volumes | Managed 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.
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.