Local docker-compose up is not enough.
Production deployment means:
A reverse proxy is a server that:
Load balancing is:
The goal is to:
Traefik:
Suitable for:
HTTPS ensures:
Let's Encrypt is:
a free, automated Certificate Authority (CA)
Most common:
a tool for obtaining and renewing certificates from Let's Encrypt.
services:
INTERNET
│ HTTPS (443)
▼
┌───────────────────┐
│ NGINX │
│ Reverse Proxy │
│ SSL Termination │
└─────────┬─────────┘
│ internal network
▼
┌───────────────────┐
│ WORDPRESS │
│ (php-fpm) │
└─────────┬─────────┘
│ internal network
▼
┌───────────────────┐
│ MARIADB │
│ Database │
└───────────────────┘
wordpress:
image: wordpress:php8.2-fpm
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: ${MYSQL_USER}
WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
volumes:
- wp_data:/var/www/html
depends_on:
- db
db:
image: mariadb:10.6
restart: always
env_file: .env
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- db_data:/var/lib/mysql
nginx:
image: nginx:latest
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- wp_data:/var/www/html
- ./nginx/conf.d:/etc/nginx/conf.d
- ./certbot/www:/var/www/certbot
- certbot_conf:/etc/letsencrypt
depends_on:
- wordpress
networks:
- internal
- public
certbot:
image: certbot/certbot:latest
volumes:
- certbot_conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
entrypoint: /bin/sh -c "trap exit TERM; while :; do certbot renew --quiet; sleep 12h & wait $${!}; done"
volumes:
db_data:
wp_data:
certbot_conf:
networks:
internal:
public:
nginx/conf.d/wordpress.conf
server {
listen 80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
root /var/www/html;
index index.php;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
fastcgi_pass wordpress:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Run once:
docker run --rm \
-v $(pwd)/certbot/conf:/etc/letsencrypt \
-v $(pwd)/certbot/www:/var/www/certbot \
certbot/certbot certonly \
--webroot \
--webroot-path=/var/www/certbot \
-d example.com -d www.example.com \
--email admin@example.com \
--agree-tos
Cron job (saved to /etc/cron.d/certbot-renew):
0 3 * * * root \
/usr/bin/docker run --rm \
-v /path/certbot/conf:/etc/letsencrypt \
-v /path/certbot/www:/var/www/certbot \
certbot/certbot renew --quiet && \
/usr/bin/docker compose -f /path/docker-compose.yml restart nginx
Monitoring and Tracking Metrics
Continuous observation of the state of an application, infrastructure, and services.
Goal:
Without monitoring we don't know that the disk is full, that a certificate has expired, that the database has crashed, that the web is returning 500 errors ...
Grafana is:
Used for:
Each metric has:
Prometheus has its own language: the Query Language – PromQL
Example:
Goal: monitor the state of the server, Docker containers and the WordPress application.
Stack:
node-exporter:
image: prom/node-exporter:latest
restart: always
pid: host
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
restart: always
privileged: true
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
prometheus:
image: prom/prometheus:latest
restart: always
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
grafana:
image: grafana/grafana:latest
restart: always
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD}
volumes:
- grafana_data:/var/lib/grafana
depends_on:
- prometheus
The goal is protection against:
Backup strategies:
docker compose exec db \
mysqldump -u root -p wordpress > backup.sql
Automation using a backup container:
backup:
image: mariadb:10.6
env_file: .env
volumes:
- ./backups:/backups
networks:
- internal
entrypoint: >
sh -c "mysqldump -h db -u root -p$$MYSQL_ROOT_PASSWORD $$MYSQL_DATABASE
> /backups/backup_$$(date +%F).sql"
depends_on:
- db
Deploying WordPress to production is not just:
docker compose up -d
It is: