Skip to content

Enfection Production Docker Stack

A production-ready Docker Compose stack for WordPress with CI/CD automation, Redis caching, optimized Nginx, and Let's Encrypt SSL.


Stack Overview

GitHub Actions CI/CD
Lightsail Ubuntu 24.04
└── Docker Compose
    ├── MariaDB 10.11       — database
    ├── Redis 7 Alpine      — object cache
    ├── WordPress PHP 8.3   — application (PHP-FPM)
    └── Nginx Stable        — web server (port 80/443)

What's Included

Component Purpose
MariaDB 10.11 Database — 20-30% faster than MySQL 8.0
WordPress 6.7 PHP 8.3 FPM Application server
Redis 7 Alpine Object caching
Nginx Stable Alpine Web server with gzip, security headers, rate limiting
Certbot Let's Encrypt SSL automation (production domain)
PHP uploads.ini 256MB memory, 64MB uploads, 300s timeout

Nginx Optimizations

  • Gzip compression — reduces page size 3x
  • Security headers — X-Frame-Options, X-XSS-Protection, X-Content-Type-Options
  • Rate limiting — wp-login.php max 5 requests/min (brute force protection)
  • Static file caching — 30 days + Cache-Control immutable
  • FastCGI buffers — 16x16k, faster PHP response handling
  • Blocked — xmlrpc.php, PHP execution in uploads/

Structure

docker/
├── docker-compose.yml          — full stack definition
├── nginx.conf                  — optimized nginx config
└── php/
    └── uploads.ini             — PHP tuning

.github/workflows/
├── deploy.yml                  — staging CI/CD (SonarQube + deploy)
└── deploy-production.yml       — production CI/CD (Lightsail Docker)

Prerequisites

  • AWS Lightsail instance (Ubuntu 24.04 LTS, $10/month, 2GB RAM)
  • Docker + Docker Compose installed on server
  • GitHub repository with Actions secrets configured
  • Ports 80 and 443 open in Lightsail firewall

Lightsail Server Setup (First Time Only)

1. Create instance:

AWS Lightsail → Create instance
→ OS only → Ubuntu 24.04 LTS → $10/month
→ Launch script:
#!/bin/bash
apt-get update -y
apt-get install -y docker.io docker-compose
systemctl enable docker
systemctl start docker
usermod -aG docker ubuntu
→ Networking → Create Static IP → attach
→ Snapshots → Enable automatic snapshots
→ Firewall → Add HTTP (80) + HTTPS (443) rules

2. Download SSH key:

Lightsail → Account → SSH keys
→ Download: LightsailDefaultKey-ap-south-1.pem
→ Save as: C:\Users\chamo\.ssh\lightsail-production

Tip

If .pem starts with -----BEGIN RSA PRIVATE KEY----- it's already OpenSSH format. Use directly — no PuTTYgen conversion needed.

3. Set GitHub secrets:

gh secret set PROD_SERVER_IP --body "43.x.x.x" --repo Enfection-Dev-Lab/project-wordpress-theme
gh secret set PROD_SSH_KEY --body "$(cat ~/.ssh/lightsail-production)" --repo Enfection-Dev-Lab/project-wordpress-theme

First Deploy

git checkout production
git merge staging
git push origin production
# → GitHub Actions deploys → Docker stack starts automatically ✅

Wait for Actions ✅ green (~3-5 minutes).


WordPress Setup (After First Deploy)

http://[lightsail-static-ip]/wp-admin/install.php

Site Title:  [Client] Website
Username:    admin
Password:    (strong — save it!)
Email:       devops@enfection.com
→ Install WordPress

Note

Theme is activated automatically by the deploy workflow.


SSL Setup (When Domain is Ready)

After DNS is pointed to Lightsail static IP:

1. SSH into server:

ssh -i ~/.ssh/lightsail-production ubuntu@[static-ip]
cd /home/ubuntu/[client-slug]

2. Install Certbot:

sudo apt-get install -y certbot

3. Get certificate:

sudo certbot certonly --webroot \
  -w /var/www/html \
  -d yourdomain.com \
  -d www.yourdomain.com \
  --email devops@enfection.com \
  --agree-tos --non-interactive

4. Update nginx.conf for HTTPS and restart:

docker-compose restart nginx

5. Auto-renewal (cron):

echo "0 3 * * * certbot renew --quiet && docker-compose -f /home/ubuntu/[client]/docker-compose.yml restart nginx" | sudo crontab -

Subsequent Deploys

Every push to production branch automatically:

  1. Copies theme files to server
  2. Updates docker configs (nginx.conf, docker-compose.yml, php/uploads.ini)
  3. Runs docker-compose up -d --remove-orphans — picks up new services
  4. Restarts nginx — applies latest nginx.conf
  5. Activates theme via WP-CLI
  6. Flushes WordPress cache

Branch Strategy

Branch Trigger Result
staging Developer push SonarQube scan + staging deploy
production DevOps after client approval Lightsail Docker deploy
main Safety backup No deploy

Docker Stack — Container Details

MariaDB 10.11
  volume: db_data (persistent)
  network: wp_network (internal)

Redis 7 Alpine
  network: wp_network (internal)

WordPress 6.7 PHP 8.3 FPM
  volume: wp_data + theme bind mount
  php: 256MB memory, 64MB uploads, 300s timeout
  network: wp_network

Nginx Stable Alpine
  ports: 80, 443
  volume: wp_data (read-only)
  network: wp_network

Persistence

  • MariaDB data — named volume [client]_db_data
  • WordPress files — named volume [client]_wp_data
  • SSL certificates — bind mount ./certbot/conf/
  • nginx config — bind mount ./nginx.conf

Warning

Never run docker-compose down -v on production with live data. Use docker-compose down (without -v) to preserve volumes.


Troubleshooting

# Check all containers
docker-compose ps

# Nginx logs
docker-compose logs nginx --tail=50

# WordPress logs
docker-compose logs wordpress --tail=50

# Database logs
docker-compose logs db --tail=50

# Validate nginx config
docker exec [client]_nginx_1 nginx -t

# WordPress CLI
docker exec [client]_wordpress_1 wp --info --allow-root

Common issues:

Symptom Cause Fix
File not found. nginx \$uri escaping bug Fix nginx.conf: \$uri$uri
ERR_CONNECTION_REFUSED Port 80 not open Lightsail firewall → add HTTP rule
DB container exit MySQL/MariaDB volume incompatibility docker-compose down -v && docker-compose up -d
Theme not activating WP not installed yet Complete install.php first

PHP Version Compatibility

PHP WordPress Recommendation
8.3 6.8+ ✅ Best choice (current default)
8.2 6.5+ ✅ Safe choice
8.1 6.4+ Safe choice
8.0 6.4+ Acceptable, older

To change PHP version — update docker/docker-compose.yml:

image: wordpress:6.7-php8.3-fpm  # change 8.3 → 8.2 etc.