Automated Deployment with GitHub Actions: CI/CD Pipeline Setup
Build CI/CD pipelines with GitHub Actions: Docker build and push, SSH server deployment, test automation, secrets management, and workflow optimization.
Define and manage multi-container applications with Docker Compose: service dependencies, environment variables, health checks, scaling, and production deployment. Comprehensive guide.
Docker Compose is a tool that lets you define and manage multiple Docker containers using a single YAML file. It's essential for microservice architectures, web applications, and development environments.
With Docker Compose you can:
docker-compose.yml fileDocker Compose v2 runs with the docker compose command (no hyphen) and is integrated into Docker CLI. The legacy docker-compose command is still supported.
version: '3.9'
services:
web:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./html:/usr/share/nginx/html:ro
restart: unless-stopped
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret_password
MYSQL_DATABASE: myapp
volumes:
- db_data:/var/lib/mysql
restart: unless-stopped
volumes:
db_data:
services:
app:
build:
context: ./app
dockerfile: Dockerfile
image: myapp:latest
container_name: app-container
ports:
- "3000:3000"
environment:
NODE_ENV: production
DB_HOST: database
depends_on:
database:
condition: service_healthy
networks:
- app-network
restart: unless-stopped
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
volumes:
# Named volume (managed by Docker)
db_data:
driver: local
# External volume (pre-created)
existing_volume:
external: true
# Custom driver options
nfs_volume:
driver: local
driver_opts:
type: nfs
o: addr=192.168.1.100,rw
device: ":/nfs/share"
networks:
# Default bridge network
app-network:
driver: bridge
# With custom subnet
custom-net:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
# External network
existing-net:
external: true
Control service startup order with depends_on:
services:
web:
image: nginx:alpine
depends_on:
api:
condition: service_healthy
db:
condition: service_healthy
api:
build: ./api
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
depends_on:
db:
condition: service_healthy
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
depends_on only controls startup order. Without condition: service_healthy you cannot guarantee the service is ready.
services:
app:
image: myapp:latest
environment:
- NODE_ENV=production
- PORT=3000
- DB_HOST=database
- DB_PORT=5432
services:
app:
image: myapp:latest
env_file:
- .env
- .env.production
.env file:
# .env
NODE_ENV=production
DB_HOST=database
DB_PORT=5432
DB_NAME=myapp
DB_USER=user
DB_PASS=secret_password
JWT_SECRET=very_secret_key
REDIS_URL=redis://redis:6379
Add .env to .gitignore. Never commit sensitive information to Git.
You can horizontally scale services with Docker Compose:
# Start api service with 3 instances
docker compose up -d --scale api=3
# Change running instance count
docker compose up -d --scale api=5
# Scale a specific service
docker compose scale api=3 worker=2
Load balancer configuration for scaling:
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- api
api:
build: ./api
# Don't define ports - nginx will route
expose:
- "3000"
environment:
NODE_ENV: production
Use override files for different environments:
# docker-compose.override.yml (development)
services:
app:
build:
context: .
target: development
volumes:
- .:/app
- /app/node_modules
environment:
NODE_ENV: development
command: npm run dev
db:
ports:
- "5432:5432" # Expose externally in development
# docker-compose.prod.yml (production)
services:
app:
image: registry.example.com/myapp:latest
restart: always
deploy:
replicas: 3
resources:
limits:
cpus: '1.0'
memory: 1G
Usage:
# Development (override applied automatically)
docker compose up -d
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
version: '3.9'
services:
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- certbot_data:/etc/letsencrypt:ro
depends_on:
- api
restart: always
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
api:
image: registry.example.com/api:${APP_VERSION:-latest}
env_file: .env.production
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
restart: always
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
db:
image: postgres:15-alpine
env_file: .env.production
volumes:
- postgres_data:/var/lib/postgresql/data
restart: always
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redis_data:/data
restart: always
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
volumes:
postgres_data:
redis_data:
certbot_data:
# Start all services
docker compose up -d
# Start specific service
docker compose up -d api
# Follow logs
docker compose logs -f
docker compose logs -f api
# Stop services
docker compose down
# Stop services and remove volumes
docker compose down -v
# Restart a service
docker compose restart api
# View service status
docker compose ps
# Execute command in service
docker compose exec api bash
# Rebuild images
docker compose build --no-cache
docker compose up -d --build
# Validate configuration
docker compose config
Docker Compose is the most practical way to manage multi-container applications. It provides a consistent environment from development to production. With override files you can flexibly configure different environments, and with healthcheck and depends_on you can safely manage service dependencies.
'docker compose config' shows the final configuration after all override files and environment variables are applied. It's useful for debugging and verifying that your configuration is correct.
'depends_on' by default only waits for the container to start, not for it to be ready. Use 'condition: service_healthy' to wait until the healthcheck passes. It's also recommended to add retry logic in your application.
Docker Compose is suitable for single-server deployments. If you need multi-server orchestration, Kubernetes or Docker Swarm is preferred. Docker Compose is widely used in production for small to medium-scale applications.
Docker Compose automatically reads the .env file in the same directory. Access values with '${VARIABLE_NAME}' syntax. For example: 'image: myapp:${APP_VERSION:-latest}'. Use ':-' for default values.
Use 'expose' instead of 'ports' for scaled services. Expose only opens the port to the internal network. Route external traffic through a load balancer (nginx, traefik). This way multiple instances can use the same internal port.
'docker compose stop' stops containers but doesn't remove them. 'docker compose down' stops and removes containers. Adding the '-v' flag also removes volumes. 'stop' is recommended for development, use 'down' carefully in production.
Build CI/CD pipelines with GitHub Actions: Docker build and push, SSH server deployment, test automation, secrets management, and workflow optimization.
Ansible installation, inventory file, playbook writing, role structure, variables, encrypted vault, ad-hoc commands, and server configuration automation. Step-by-step guide.
Caddy web server installation, Caddyfile configuration, automatic Let's Encrypt SSL, reverse proxy, static site hosting, PHP-FPM integration, and performance settings.