Last updated: December 2025
When applications grow beyond a single container, managing them with long docker run commands becomes difficult. Real-world applications are rarely made up of just one container. You often need a web server, a backend service, a database, maybe a cache, and all of them must communicate reliably. This is where Docker Compose shines.
Docker Compose allows you to define, configure, and run multi-container applications using a single YAML file. Instead of managing containers one by one, you describe the entire application stack declaratively and let Docker handle the rest.
What Is Docker Compose?
Docker Compose is a tool that lets you:
- Define multiple containers (services)
- Specify networks, volumes and environment variables.
- Control startup order and dependencies
- Run/start everything with a single command
- Use a declarative YAML file instead of imperative CLI commands
Why Docker Compose Exists?
Without Docker Compose, running a simple app might look like this:
docker network create app-net
docker volume create db-data
docker run -d --name db --network app-net -v db-data:/var/lib/mysql mysql
docker run -d --name api --network app-net api-image
docker run -d -p 80:80 --name web --network app-net nginx
docker compose up -d
Docker Compose replaces imperative commands with a declarative configuration.
Core Concepts in Docker Compose
1. Services
A service represents a container (or group of identical containers) in your application.
Examples:
webapidb
Each service maps directly to a container configuration.
2. Networks
Docker Compose automatically creates a bridge network for your application by default except specified otherwise.
Key benefits:
- Containers can communicate using service names as hostnames
- Networks are isolated per application
Example:
services:
web:
image: nginx
api:
image: my-api
3. Volumes
Volumes are used to persist data beyond the lifecycle of a container.
Typical use cases:
- Databases
- Uploaded files
- Application state
Example:
services:
db:
image: mysql
volumes:
- db-data:/var/lib/mysql
volumes:
db-data:
Anatomy/Structure of a docker-compose.yml File
Here’s a simple example:
version: "3.9" # This section specifies the Docker version
services: # This section specifies all the applications(containers)
web: # Application 1 name (frontend service)
image: nginx:latest # Docker image used for the web service
ports: # Exposes container ports to the host machine
- "80:80" # Host port 80 mapped to container port 80
depends_on: # Ensures the API service starts before this service
- api
api: # Application 2 name (backend service)
image: my-api:latest # Custom backend application image
expose: # Exposes port only within the Docker network
- "8080" # API listens on port 8080 internally
db: # Application 3 name (database service)
image: postgres:15 # PostgreSQL database image
environment: # Environment variables passed into the container
POSTGRES_PASSWORD: secret # Database password
volumes: # Mounts persistent storage into the container
- db-data:/var/lib/postgresql/data #Stores DB data outside the container
volumes: # This section defines volumes to be created
db-data: # Docker-managed volume for database persistence
What This Does
- Starts three containers:
web,api, anddb - Creates a private network for them
- Exposes only the web service to the host
- Persists database data using a volume
Running a Docker Compose Application
From the directory containing the compose file, run the command:
docker compose up -d
Useful commands:
docker compose ps # List running services
docker compose logs # View logs
docker compose stop # Stop services
docker compose down # Stop and remove containers
docker compose down -v # Remove containers and volumes
Other docker-compose.yaml file Examples
1. Basic Docker Compose Example (Single Service)
Here’s a simple Docker Compose file running an NGINX container:
a. create the compose file.
user1@LinuxSrv:~$ vi tekneed-website-compose.yaml
version: "3.9"
services:
web8:
image: nginx
ports:
- "8081:80"
restart: always
b. Validate the file before running if you wish
user1@LinuxSrv:~$ docker compose -f tekneed-website-compose.yaml config
WARN[0000] /home/user1/tekneed-website-compose.yaml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
name: user1
services:
web8:
image: nginx
networks:
default: null
ports:
- mode: ingress
target: 80
published: "8081"
protocol: tcp
restart: always
networks:
default:
name: user1_default
c. Start the container
user1@LinuxSrv:~$ docker compose -f tekneed-website-compose.yaml up -d
WARN[0000] /home/user1/tekneed-website-compose.yaml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
[+] Running 2/2
✔ Network user1_default Created 0.0s
✔ Container user1-web8-1 Started
NB: Because the compose file is not the default name that docker compose automatically look for which are the compose.yml and docker-compose.yml, i had to use the -f option to specify/tell docker compose where the compose file is
d. Verify that the container is up
user1@LinuxSrv:~$ docker compose -f tekneed-website-compose.yaml ps
WARN[0000] /home/user1/tekneed-website-compose.yaml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
user1-web8-1 nginx "/docker-entrypoint.…" web8 3 minutes ago Up 3 minutes 0.0.0.0:8081->80/tcp

2. Multiple Containers with a Custom Network
a. create the compose file
user1@LinuxSrv:~$ vi compose.yml
version: "3.9"
services:
web9:
image: nginx
ports:
- "8081:80"
restart: always
networks:
- web_network
web10:
image: nginx
restart: always
networks:
web_network:
ipv4_address: 10.10.10.22
networks:
web_network:
driver: bridge
ipam:
config:
- subnet: "10.10.10.0/24"
b. Validate the file before running if you wish
user1@LinuxSrv:~$ docker compose config
WARN[0000] /home/user1/compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
name: user1
services:
web9:
image: nginx
networks:
web_network: null
ports:
- mode: ingress
target: 80
published: "8081"
protocol: tcp
restart: always
web10:
image: nginx
networks:
web_network:
ipv4_address: 10.10.10.22
restart: always
networks:
web_network:
name: user1_web_network
driver: bridge
ipam:
config:
- subnet: 10.10.10.0/24
c. Start the container
user1@LinuxSrv:~$ docker compose up -d
WARN[0000] /home/user1/compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
[+] Running 0/1
[+] Running 2/31_web_network Creating 0.0s
✔ Network user1_web_network Created 0.1s
⠏ Container user1-web9-1 Starting 0.9s
✔ Container user1-web10-1 Started 0.7s
Error response from daemon: failed to set up container networking: driver failed programming external connectivity on endpoint user1-web9-1 (667dcc42d22d12066e7a6c77e72da5ffc7b6b9c3eb4afcf13ee320d28d206c2f): Bind for 0.0.0.0:8081 failed: port is already allocated
You can see an error here saying that the port is already allocated. so let’s change the port to 8089 and re-create
user1@LinuxSrv:~$ docker compose up -d
WARN[0000] /home/user1/compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
WARN[0000] Found orphan containers ([user1-web8-1]) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up.
[+] Running 2/2
✔ Container user1-web10-1 Running 0.0s
✔ Container user1-web9-1 Started
d. Verify that the container is up
user1@LinuxSrv:~$ docker compose ps
WARN[0000] /home/user1/compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
user1-web10-1 nginx "/docker-entrypoint.…" web10 4 minutes ago Up 4 minutes 80/tcp
user1-web8-1 nginx "/docker-entrypoint.…" web8 35 minutes ago Up 35 minutes 0.0.0.0:8081->80/tcp
user1-web9-1 nginx "/docker-entrypoint.…" web9 2 minutes ago Up 2 minutes 0.0.0.0:8089->80/tcp
2. Two-Tier Application Using Docker Compose WordPress + MySQL (Network + Storage)
Having understood above steps, we will just create the compose file straighaway. I will rename the above file so that I can have a new default compose file.
user1@LinuxSrv:~$ mv compose.yml compose-nginx.yml
I will now create the file.
user1@LinuxSrv:~$ vi compose.yml
version: "3.9"
services:
wordpress:
image: wordpress
ports:
- "8088:80"
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_USER: root
WORDPRESS_DB_PASSWORD: CURLf
WORDPRESS_DB_NAME: wordpress
depends_on:
- mysql
networks:
- app_network
mysql:
image: mysql:5.7
environment:
MYSQL_DATABASE: wordpress
MYSQL_ROOT_PASSWORD: CURLf
volumes:
- mysql_data:/var/lib/mysql
networks:
- app_network
networks:
app_network:
driver: bridge
volumes:
mysql_data:
Start the containers
user1@LinuxSrv:~$ docker compose up -d
WARN[0000] /home/user1/compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
[+] Running 27/37
⠙ mysql [⣿⣿⣿⣀⣿⣿⣀⣿⣿⣿⣿] 65.76MB / 136.9MB Pulling 123.2s 1.2s
✔ ffc89e9dfd88 Download complete 1.7s
⠹ 43d05e938198 Downloading [==================> ] 20.97MB/56.29MB
[+] Running 2/2
[+] Running 4/41_app_network Created 0.1s
✔ Network user1_app_network Created 0.1s
✔ Volume user1_mysql_data Created 0.0s
✔ Container user1-mysql-1 Started 2.8s
✔ Container user1-wordpress-1 Started
Verify that the container is up
user1@LinuxSrv:~$ docker compose ps
WARN[0000] /home/user1/compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
user1-mysql-1 mysql:5.7 "docker-entrypoint.s…" mysql 5 minutes ago Up 4 minutes 3306/tcp, 33060/tcp
user1-web10-1 nginx "/docker-entrypoint.…" web10 21 minutes ago Up 21 minutes 80/tcp
user1-web8-1 nginx "/docker-entrypoint.…" web8 52 minutes ago Up 52 minutes 0.0.0.0:8081->80/tcp
user1-web9-1 nginx "/docker-entrypoint.…" web9 19 minutes ago Up 19 minutes 0.0.0.0:8089->80/tcp
user1-wordpress-1 wordpress "docker-entrypoint.s…" wordpress 4 minutes ago Up 4 minutes 0.0.0.0:8088->80/tcp

Conclusion
Docker Compose:
- Simplifies multi-container applications
- Uses a single declarative YAML file
- Allows you to spin up entire environments with one command
Once you understand Docker Compose, moving to Kubernetes, Helm, OpenShift or Docker Swarm becomes much easier.
Leave a Reply