Docker Compose - Multi-Container Stack
Docker Compose allows you to define and start complete stacks (app + database + cache + proxy) with a single YAML file.
02
Installation
bash
# Docker + Compose plugin (modern method, recommended)
sudo apt update
sudo apt install docker.io docker-compose-plugin -y
# Verify
docker compose version
# Note: the command is "docker compose" (with space), not "docker-compose"
If you already have Docker installed without the plugin:
bash
# Add Compose plugin manually
DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
mkdir -p $DOCKER_CONFIG/cli-plugins
curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 \
-o $DOCKER_CONFIG/cli-plugins/docker-compose
chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
03
Basic structure of a `docker-compose.yml`
yaml
services:
app:
image: node:20-alpine
container_name: my-app
restart: unless-stopped
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DB_HOST=db
depends_on:
- db
volumes:
- ./app:/usr/src/app
db:
image: mysql:8.0
container_name: my-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: myapp
MYSQL_USER: dbuser
MYSQL_PASSWORD: dbpass
volumes:
- db_data:/var/lib/mysql
redis:
image: redis:7-alpine
container_name: my-redis
restart: unless-stopped
volumes:
db_data:
04
Essential commands
bash
# Start everything (in background)
docker compose up -d
# Start and rebuild images
docker compose up -d --build
# Stop everything
docker compose down
# Stop and remove volumes (WARNING: deletes data)
docker compose down -v
# See logs
docker compose logs -f
docker compose logs -f app # only one service
# Container status
docker compose ps
# Run a command in a container
docker compose exec app bash
docker compose exec db mysql -u root -p
# Restart a single service
docker compose restart app
# Scale a service (multiple instances)
docker compose up -d --scale app=3
05
Example: WordPress + MySQL + Nginx
yaml
# docker-compose.yml
services:
nginx:
image: nginx:alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- wordpress_data:/var/www/html
depends_on:
- wordpress
wordpress:
image: wordpress:php8.2-fpm-alpine
restart: unless-stopped
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: wppassword
volumes:
- wordpress_data:/var/www/html
db:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: wppassword
MYSQL_ROOT_PASSWORD: rootpassword
volumes:
- db_data:/var/lib/mysql
volumes:
wordpress_data:
db_data:
06
Example: Node.js + PostgreSQL + Redis
yaml
services:
api:
build: .
restart: unless-stopped
ports:
- "3000:3000"
env_file: .env
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: myapp
POSTGRES_USER: myuser
POSTGRES_PASSWORD: mypassword
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U myuser -d myapp"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: unless-stopped
command: redis-server --requirepass redispassword
volumes:
pg_data:
07
Environment variables with `.env`
bash
# .env (same path as docker-compose.yml)
DB_PASSWORD=secure_password
APP_PORT=3000
yaml
# docker-compose.yml
services:
app:
environment:
- DB_PASSWORD=${DB_PASSWORD}
- APP_PORT=${APP_PORT}
Never commit .env file to Git. Add it to .gitignore.
08
Networking between containers
Containers from the same docker-compose.yml can communicate using the service name as hostname:
yaml
# The "app" service connects to the database using "db:5432"
environment:
DB_HOST: db # service name, not localhost!
DB_PORT: 5432
09
Automatic startup on boot
With restart: unless-stopped or restart: always, containers automatically restart. Docker must be enabled as a service:
bash
sudo systemctl enable docker
10
Update containers
bash
# Download new images
docker compose pull
# Recreate containers with new images
docker compose up -d --pull always
# Clean up old images
docker image prune -f
Related articles
