Docker Compose Orchestration: Introduction and Setup
Docker Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services, networks, and volumes, then create and start all services with a single command.
What is Docker Compose?
Docker Compose solves the complexity of managing multiple containers by providing:
- Declarative Configuration: Define your entire application stack in a single YAML file
- Service Orchestration: Manage dependencies between containers
- Environment Management: Easy switching between development, testing, and production
- Scaling: Scale services up or down with simple commands
- Networking: Automatic network creation and service discovery
Installation and Setup
Installing Docker Compose
Linux:
# Download the latest version
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# Make it executable
sudo chmod +x /usr/local/bin/docker-compose
# Verify installation
docker-compose --version
macOS (with Homebrew):
brew install docker-compose
Windows: Docker Compose is included with Docker Desktop for Windows.
Compose File Structure
A basic docker-compose.yml
file structure:
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
volumes:
- .:/app
environment:
- DEBUG=1
depends_on:
- db
db:
image: postgres:13
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
networks:
default:
driver: bridge
Your First Compose Application
Let’s create a simple web application with a database:
Project Structure
my-app/
├── docker-compose.yml
├── Dockerfile
├── app.py
├── requirements.txt
└── templates/
└── index.html
Flask Application
app.py:
from flask import Flask, render_template
import psycopg2
import os
app = Flask(__name__)
def get_db_connection():
conn = psycopg2.connect(
host=os.environ.get('DB_HOST', 'db'),
database=os.environ.get('DB_NAME', 'myapp'),
user=os.environ.get('DB_USER', 'user'),
password=os.environ.get('DB_PASSWORD', 'password')
)
return conn
@app.route('/')
def index():
try:
conn = get_db_connection()
cur = conn.cursor()
cur.execute('SELECT version();')
db_version = cur.fetchone()
cur.close()
conn.close()
return render_template('index.html', db_version=db_version[0])
except Exception as e:
return f"Database connection failed: {str(e)}"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
requirements.txt:
Flask==2.3.3
psycopg2-binary==2.9.7
templates/index.html:
<!DOCTYPE html>
<html>
<head>
<title>Docker Compose App</title>
</head>
<body>
<h1>Hello from Docker Compose!</h1>
<p>Database Version: {{ db_version }}</p>
</body>
</html>
Dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "app.py"]
Docker Compose Configuration
docker-compose.yml:
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
volumes:
- .:/app
environment:
- DB_HOST=db
- DB_NAME=myapp
- DB_USER=user
- DB_PASSWORD=password
depends_on:
- db
restart: unless-stopped
db:
image: postgres:13
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
restart: unless-stopped
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
volumes:
postgres_data:
redis_data:
networks:
default:
driver: bridge
Essential Compose Commands
Basic Operations
# Start all services
docker-compose up
# Start in detached mode
docker-compose up -d
# Build and start
docker-compose up --build
# Stop all services
docker-compose down
# Stop and remove volumes
docker-compose down -v
# View running services
docker-compose ps
# View logs
docker-compose logs
# Follow logs for specific service
docker-compose logs -f web
Service Management
# Start specific service
docker-compose start web
# Stop specific service
docker-compose stop web
# Restart service
docker-compose restart web
# Scale service
docker-compose up --scale web=3
# Execute command in running container
docker-compose exec web bash
# Run one-off command
docker-compose run web python manage.py migrate
Environment Configuration
Environment Files
Create .env
file for environment variables:
# .env
POSTGRES_DB=myapp
POSTGRES_USER=user
POSTGRES_PASSWORD=secretpassword
DEBUG=1
SECRET_KEY=your-secret-key
Update docker-compose.yml
:
version: '3.8'
services:
web:
build: .
ports:
- "8000:8000"
env_file:
- .env
environment:
- DB_HOST=db
depends_on:
- db
db:
image: postgres:13
env_file:
- .env
volumes:
- postgres_data:/var/lib/postgresql/data
Multiple Environment Files
# docker-compose.override.yml (development)
version: '3.8'
services:
web:
volumes:
- .:/app
environment:
- DEBUG=1
command: python app.py
db:
ports:
- "5432:5432"
# docker-compose.prod.yml (production)
version: '3.8'
services:
web:
environment:
- DEBUG=0
restart: always
command: gunicorn --bind 0.0.0.0:8000 app:app
db:
restart: always
Run with specific configuration:
# Development (uses docker-compose.override.yml automatically)
docker-compose up
# Production
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
Networking in Compose
Default Network
Compose automatically creates a default network for your application:
version: '3.8'
services:
web:
image: nginx
# Can communicate with 'api' service using hostname 'api'
api:
image: node:16
# Can communicate with 'web' service using hostname 'web'
Custom Networks
version: '3.8'
services:
web:
image: nginx
networks:
- frontend
- backend
api:
image: node:16
networks:
- backend
db:
image: postgres:13
networks:
- backend
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # No external access
External Networks
version: '3.8'
services:
web:
image: nginx
networks:
- existing-network
networks:
existing-network:
external: true
Volume Management
Named Volumes
version: '3.8'
services:
db:
image: postgres:13
volumes:
- postgres_data:/var/lib/postgresql/data
- postgres_config:/etc/postgresql
volumes:
postgres_data:
driver: local
postgres_config:
driver: local
driver_opts:
type: none
o: bind
device: /host/path/to/config
Bind Mounts
version: '3.8'
services:
web:
image: nginx
volumes:
- ./html:/usr/share/nginx/html:ro # Read-only
- ./logs:/var/log/nginx:rw # Read-write
- /etc/localtime:/etc/localtime:ro # Host timezone
External Volumes
version: '3.8'
services:
db:
image: postgres:13
volumes:
- existing_volume:/var/lib/postgresql/data
volumes:
existing_volume:
external: true
Health Checks and Dependencies
Health Checks
version: '3.8'
services:
web:
build: .
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
db:
image: postgres:13
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 10s
timeout: 5s
retries: 5
Service Dependencies
version: '3.8'
services:
web:
build: .
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
db:
image: postgres:13
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
Practical Example: Development Environment
Let’s create a complete development environment:
docker-compose.dev.yml:
version: '3.8'
services:
# Frontend Development Server
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules
environment:
- REACT_APP_API_URL=http://localhost:8000
command: npm start
# Backend API
backend:
build:
context: ./backend
dockerfile: Dockerfile.dev
ports:
- "8000:8000"
volumes:
- ./backend:/app
environment:
- DATABASE_URL=postgresql://user:password@db:5432/devdb
- REDIS_URL=redis://redis:6379
- DEBUG=1
depends_on:
- db
- redis
command: python manage.py runserver 0.0.0.0:8000
# Database
db:
image: postgres:13
environment:
POSTGRES_DB: devdb
POSTGRES_USER: user
POSTGRES_PASSWORD: password
volumes:
- postgres_dev_data:/var/lib/postgresql/data
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
# Redis Cache
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_dev_data:/data
# Database Admin Interface
pgadmin:
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: admin
ports:
- "5050:80"
volumes:
- pgadmin_data:/var/lib/pgadmin
volumes:
postgres_dev_data:
redis_dev_data:
pgadmin_data:
Start the development environment:
docker-compose -f docker-compose.dev.yml up --build
Summary
In this introduction, you’ve learned:
Core Concepts
- Docker Compose Purpose: Orchestrating multi-container applications
- YAML Configuration: Declarative service definitions
- Service Dependencies: Managing container startup order
- Environment Management: Different configurations for different stages
Essential Skills
- Installation and Setup: Getting Compose ready for development
- Basic Commands: Starting, stopping, and managing services
- Networking: Service discovery and custom networks
- Volume Management: Persistent data and bind mounts
- Health Checks: Ensuring service reliability
Practical Applications
- Development Environment: Complete local development stack
- Environment Configuration: Using .env files and overrides
- Service Communication: Inter-container networking
- Data Persistence: Volume management strategies
Next Steps: In Part 2, we’ll dive deeper into core concepts including advanced service configuration, networking patterns, and volume strategies that form the foundation of production-ready Compose applications.