Docker Compose — Practice Projects — Exercises
Five hands-on challenges that test your ability to design and build multi-container applications from scratch.
Exercise 1 — Monitoring Stack
Task: Create a compose.yml that runs Prometheus, Grafana, and a Node Exporter for host metrics collection.
Requirements:
- Prometheus scrapes Node Exporter every 15 seconds
- Grafana connects to Prometheus as a data source
- Grafana is accessible on port 3000
- Prometheus is accessible on port 9090
- All data persists in named volumes
Show Solution
services:
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- prom-data:/prometheus
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
networks:
- monitor-net
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
environment:
GF_SECURITY_ADMIN_PASSWORD: admin
depends_on:
- prometheus
networks:
- monitor-net
node-exporter:
image: prom/node-exporter:latest
expose:
- "9100"
networks:
- monitor-net
volumes:
prom-data:
grafana-data:
networks:
monitor-net:
driver: bridge
You also need prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
Exercise 2 — GitLab Runner Environment
Task: Create a Compose setup that runs a mock CI environment with a test runner service, a database for tests, and a Redis for test caching.
Requirements:
- Test runner builds from a local Dockerfile
- Database starts with health check
- Redis starts with health check
- Test runner depends on both being healthy
- Use an
.envfile for all credentials
Show Solution
.env:
DB_NAME=testdb
DB_USER=testuser
DB_PASS=testpass
REDIS_URL=redis://redis:6379
services:
test-runner:
build: ./runner
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
environment:
DATABASE_URL: postgres://${DB_USER}:${DB_PASS}@db:5432/${DB_NAME}
REDIS_URL: ${REDIS_URL}
networks:
- ci-net
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASS}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 5s
timeout: 3s
retries: 5
networks:
- ci-net
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
networks:
- ci-net
networks:
ci-net:
driver: bridge
Exercise 3 — Multi-Language Microservices
Task: Create a Compose file that runs three microservices: a Python API, a Node.js API, and a Go API, all behind an Nginx reverse proxy.
Requirements:
- Each service exposes different endpoints
- Nginx routes based on URL path (
/python/,/node/,/go/) - All services share a common network
- Nginx is the only service with a published port
Show Solution
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- python-api
- node-api
- go-api
networks:
- micro-net
python-api:
build: ./python-service
expose:
- "8000"
networks:
- micro-net
node-api:
build: ./node-service
expose:
- "3000"
networks:
- micro-net
go-api:
build: ./go-service
expose:
- "8080"
networks:
- micro-net
networks:
micro-net:
driver: bridge
nginx.conf:
server {
listen 80;
location /python/ {
proxy_pass http://python-api:8000/;
}
location /node/ {
proxy_pass http://node-api:3000/;
}
location /go/ {
proxy_pass http://go-api:8080/;
}
}
Exercise 4 — Database Replication
Task: Set up a PostgreSQL primary-replica pair using Compose.
Requirements:
- Primary accepts read/write connections
- Replica streams from primary
- Both have health checks
- Primary data persists in a named volume
Show Solution
services:
primary:
image: postgres:16-alpine
environment:
POSTGRES_DB: mydb
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
POSTGRES_INITDB_ARGS: "--wal-segsize=64"
command: >
postgres
-c wal_level=replica
-c max_wal_senders=3
-c hot_standby=on
volumes:
- primary-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin"]
interval: 5s
timeout: 3s
retries: 5
networks:
- db-net
replica:
image: postgres:16-alpine
depends_on:
primary:
condition: service_healthy
environment:
PGUSER: admin
PGPASSWORD: secret
command: >
bash -c "
rm -rf /var/lib/postgresql/data/* &&
pg_basebackup -h primary -D /var/lib/postgresql/data -U admin -Fp -Xs -P &&
echo 'primary_conninfo = host=primary port=5432 user=admin password=secret' >> /var/lib/postgresql/data/postgresql.auto.conf &&
touch /var/lib/postgresql/data/standby.signal &&
postgres
"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin"]
interval: 5s
timeout: 3s
retries: 10
start_period: 30s
networks:
- db-net
volumes:
primary-data:
networks:
db-net:
driver: bridge
Exercise 5 — Complete SaaS Template
Task: Design a compose.yml for a SaaS application with: frontend, API, worker, database, cache, queue, and object storage.
Requirements:
- Frontend builds from
./frontend - API builds from
./api - Worker builds from
./workerand shares API’s environment - PostgreSQL for persistence
- Redis for caching
- RabbitMQ for message queue
- MinIO for object storage
- Use health checks on all infrastructure services
- Use profiles:
monitoringfor Grafana,debugfor Adminer
Show Solution
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
depends_on:
- api
networks:
- app-net
api:
build: ./api
ports:
- "8000:8000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
rabbitmq:
condition: service_healthy
environment:
DATABASE_URL: postgres://appuser:apppass@postgres:5432/appdb
REDIS_URL: redis://redis:6379
AMQP_URL: amqp://guest:guest@rabbitmq:5672
S3_ENDPOINT: http://minio:9000
networks:
- app-net
worker:
build: ./worker
depends_on:
rabbitmq:
condition: service_healthy
environment:
DATABASE_URL: postgres://appuser:apppass@postgres:5432/appdb
REDIS_URL: redis://redis:6379
AMQP_URL: amqp://guest:guest@rabbitmq:5672
networks:
- app-net
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: apppass
volumes:
- pg-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 5s
timeout: 3s
retries: 5
networks:
- app-net
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
networks:
- app-net
rabbitmq:
image: rabbitmq:3-management-alpine
ports:
- "15672:15672"
volumes:
- rabbit-data:/var/lib/rabbitmq
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "check_running"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-net
minio:
image: minio/minio:latest
command: server /data --console-address ":9001"
ports:
- "9000:9000"
- "9001:9001"
volumes:
- minio-data:/data
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-net
grafana:
image: grafana/grafana:latest
profiles:
- monitoring
ports:
- "3001:3000"
networks:
- app-net
adminer:
image: adminer:latest
profiles:
- debug
ports:
- "8080:8080"
networks:
- app-net
volumes:
pg-data:
redis-data:
rabbit-data:
minio-data:
networks:
app-net:
driver: bridge