Quick Answer: Create a
compose.ymlfile, define your services, rundocker compose up -d. Example: WordPress + MySQL in 10 seconds — copy the WordPress stack below.
Need a VPS? Vultr (free credit), DigitalOcean ($200 free credit), or RackNerd (cheap annual deals).
How Compose Works
A compose.yml file defines multiple containers that work together:
# Start all services
docker compose up -d
# Stop all services
docker compose down
# View logs
docker compose logs -f
# Rebuild and restart
docker compose up -d --build
# List running services
docker compose ps
WordPress + MySQL
services:
wordpress:
image: wordpress:latest
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: secret
WORDPRESS_DB_NAME: wordpress
volumes:
- wp_data:/var/www/html
depends_on:
- db
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: rootsecret
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: secret
volumes:
- db_data:/var/lib/mysql
volumes:
wp_data:
db_data:
Visit http://localhost:8080 after starting.
Nginx + Node.js + PostgreSQL
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
depends_on:
- api
api:
build: ./api
environment:
DATABASE_URL: postgres://app:secret@db:5432/myapp
NODE_ENV: production
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
volumes:
- pg_data:/var/lib/postgresql/data
volumes:
pg_data:
nginx.conf:
server {
listen 80;
location / {
proxy_pass http://api:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
PostgreSQL + pgAdmin
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
volumes:
- pg_data:/var/lib/postgresql/data
pgadmin:
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: admin
ports:
- "5050:80"
depends_on:
- db
volumes:
pg_data:
pgAdmin at http://localhost:5050. Connect to host db, port 5432.
Redis + Redis Commander
services:
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --requirepass secret
redis-commander:
image: rediscommander/redis-commander
environment:
REDIS_HOSTS: local:redis:6379:0:secret
ports:
- "8081:8081"
depends_on:
- redis
volumes:
redis_data:
MongoDB + Mongo Express
services:
mongo:
image: mongo:7
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: secret
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db
mongo-express:
image: mongo-express
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: admin
ME_CONFIG_MONGODB_ADMINPASSWORD: secret
ME_CONFIG_MONGODB_URL: mongodb://admin:secret@mongo:27017/
ports:
- "8081:8081"
depends_on:
- mongo
volumes:
mongo_data:
Traefik Reverse Proxy + SSL
Auto-SSL with Let's Encrypt for all your services:
services:
traefik:
image: traefik:v3.0
command:
- "--api.insecure=true"
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.le.acme.httpchallenge=true"
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
- "[email protected]"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- letsencrypt:/letsencrypt
myapp:
image: nginx:alpine
labels:
- "traefik.http.routers.myapp.rule=Host(`app.example.com`)"
- "traefik.http.routers.myapp.tls.certresolver=le"
volumes:
letsencrypt:
Every service with Traefik labels gets automatic SSL.
Prometheus + Grafana (Monitoring)
services:
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prom_data:/prometheus
ports:
- "9090:9090"
grafana:
image: grafana/grafana
environment:
GF_SECURITY_ADMIN_PASSWORD: admin
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
depends_on:
- prometheus
node-exporter:
image: prom/node-exporter
ports:
- "9100:9100"
volumes:
prom_data:
grafana_data:
prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'node'
static_configs:
- targets: ['node-exporter:9100']
Development Environment (Full Stack)
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules
environment:
- REACT_APP_API_URL=http://localhost:4000
backend:
build: ./backend
ports:
- "4000:4000"
volumes:
- ./backend:/app
- /app/node_modules
environment:
- DATABASE_URL=postgres://dev:dev@db:5432/devdb
- REDIS_URL=redis://redis:6379
depends_on:
- db
- redis
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: devdb
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
ports:
- "5432:5432"
volumes:
- pg_data:/var/lib/postgresql/data
redis:
image: redis:alpine
ports:
- "6379:6379"
volumes:
pg_data:
Useful Compose Patterns
Health Checks
services:
api:
image: myapi
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
Resource Limits
services:
api:
image: myapi
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
memory: 256M
Restart Policies
services:
api:
image: myapi
restart: unless-stopped
# Options: no, always, on-failure, unless-stopped
Environment Files
services:
api:
image: myapi
env_file:
- .env