Orchestrating High-Availability WordPress with Docker Swarm and AWS Load Balancing: A Deep Dive into Infrastructure as Code
Docker Swarm Service Definition for WordPress
To achieve high availability for WordPress, we’ll leverage Docker Swarm’s declarative service model. This allows us to define the desired state of our application, and Swarm will ensure it’s maintained. We’ll define separate services for the WordPress application itself, the PHP-FPM backend, and the database. For this example, we’ll use MariaDB as our database, a common and robust choice for WordPress.
The core of our Swarm deployment will be a docker-compose.yml file. This file will define our services, networks, and volumes. We’ll ensure that WordPress and PHP-FPM are configured to communicate over a shared Docker network and that persistent data is managed via named volumes.
WordPress and PHP-FPM Service Configuration
The WordPress application service will primarily serve static assets and act as a reverse proxy to the PHP-FPM service for dynamic requests. We’ll use an official Nginx image as the web server. The PHP-FPM service will handle the WordPress PHP execution. We’ll use the official PHP-FPM image, configured to listen on a specific port that Nginx can connect to.
docker-compose.yml – Services Section
Here’s a snippet of the docker-compose.yml file focusing on the WordPress and PHP-FPM services:
version: '3.8'
services:
wordpress:
image: nginx:alpine
ports:
- "80:80"
volumes:
- wordpress_data:/var/www/html
- ./nginx/conf.d:/etc/nginx/conf.d
networks:
- wordpress_net
deploy:
replicas: 3
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
depends_on:
- php-fpm
php-fpm:
image: php:8.2-fpm-alpine
volumes:
- wordpress_data:/var/www/html
- ./php/conf.d:/usr/local/etc/php/conf.d
networks:
- wordpress_net
deploy:
replicas: 3
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
depends_on:
- db
volumes:
wordpress_data:
networks:
wordpress_net:
Nginx Configuration for WordPress
The Nginx configuration is crucial for routing traffic correctly. It needs to serve static files directly and proxy PHP requests to the PHP-FPM service. We’ll configure Nginx to use the service name php-fpm as the upstream server, allowing Docker Swarm’s internal DNS to resolve it.
nginx/conf.d/default.conf
Create a file named default.conf within an nginx/conf.d/ directory in your project root:
server {
listen 80;
server_name localhost; # Or your domain name
root /var/www/html;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php-fpm:9000; # Docker Swarm service name and port
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
# Caching for static assets
location ~* \.(jpg|jpeg|gif|png|css|js|ico|xml|txt)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
}
Database Service Configuration (MariaDB)
For the database, we’ll use a MariaDB image. It’s essential to configure persistent storage for the database data. We’ll also set environment variables for root password and database creation. For production, consider using AWS RDS or a managed database service instead of self-hosting within Swarm for better reliability and scalability.
docker-compose.yml – Database Section
db:
image: mariadb:10.6
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-supersecretrootpass}
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: ${MYSQL_PASSWORD:-supersecretuserpass}
networks:
- wordpress_net
deploy:
replicas: 1 # Databases typically don't scale horizontally in Swarm without complex setups
restart_policy:
condition: on-failure
volumes:
wordpress_data:
db_data: # Persistent volume for database
# ... rest of the docker-compose.yml
Note the use of environment variables for sensitive information like passwords. These should ideally be managed using Docker Secrets or AWS Secrets Manager in a production environment.
Deploying to Docker Swarm
Once your docker-compose.yml file and Nginx configuration are in place, you can deploy your WordPress stack to your Docker Swarm cluster. Ensure you have a Swarm initialized and your nodes are ready.
Initialization and Deployment Commands
First, initialize your Docker Swarm if you haven’t already:
# On your manager node docker swarm init --advertise-addr
Then, deploy your stack from the directory containing your docker-compose.yml file:
# Ensure you have .env file with MYSQL_ROOT_PASSWORD and MYSQL_PASSWORD if not hardcoded docker stack deploy -c docker-compose.yml wordpress_stack
You can check the status of your services:
docker stack services wordpress_stack docker stack ps wordpress_stack
Integrating with AWS Load Balancing
Docker Swarm’s built-in ingress routing mesh is useful for internal load balancing, but for external access and integration with AWS services, an AWS Elastic Load Balancer (ELB) is necessary. We’ll use an Application Load Balancer (ALB) for its advanced routing capabilities and SSL termination.
ALB Target Group Configuration
You’ll need to create an ALB and configure its target groups. The target groups should point to the Docker Swarm nodes on the port exposed by the Swarm ingress. By default, Swarm exposes published ports on all nodes. For a service with ports: - "80:80", Swarm makes port 80 available on all nodes.
Target Group Setup:
- Protocol: HTTP
- Port: 80 (or the port your Swarm ingress is listening on for published ports)
- Target Type: Instance
- VPC: Your AWS VPC
- Health Checks: Configure health checks to point to a specific URL on your WordPress site (e.g.,
/wp-cron.phpor a custom health check endpoint) with an expected status code of 200. This ensures the ALB only routes traffic to healthy WordPress instances.
ALB Listener and Rules
Configure the ALB listener (typically on port 80 and 443 for HTTPS) to forward traffic to the target group you created. For HTTPS, you’ll need to attach an SSL certificate from AWS Certificate Manager (ACM).
Security Group Configuration
Ensure your EC2 instances running Docker Swarm have security groups that allow inbound traffic on the Swarm ingress port (typically 4789 for Swarm control plane, and the published port, e.g., 80/443, for application traffic) from the ALB’s security group. The ALB’s security group should allow inbound traffic on port 80/443 from your desired client IP ranges (e.g., 0.0.0.0/0).
Infrastructure as Code (IaC) with Terraform
To manage this infrastructure reliably and reproducibly, we’ll use Terraform. This allows us to define our AWS resources (VPC, EC2 instances, ALB, Security Groups, etc.) in code.
Terraform Configuration Snippet (AWS Provider and EC2)
Here’s a simplified example of how you might define your AWS provider and EC2 instances for your Docker Swarm nodes:
provider "aws" {
region = "us-east-1"
}
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = {
Name = "wordpress-swarm-vpc"
}
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
tags = {
Name = "wordpress-swarm-public-subnet"
}
}
resource "aws_security_group" "swarm_nodes" {
name = "swarm-nodes-sg"
description = "Allow Swarm traffic and ALB access"
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # Restrict this in production
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb.id] # Allow ALB
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [aws_security_group.alb.id] # Allow ALB
}
# Swarm management ports (adjust as needed)
ingress {
from_port = 2377
to_port = 2377
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # Restrict this
}
ingress {
from_port = 7946
to_port = 7946
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # Restrict this
}
ingress {
from_port = 7946
to_port = 7946
protocol = "udp"
cidr_blocks = ["0.0.0.0/0"] # Restrict this
}
ingress {
from_port = 4789
to_port = 4789
protocol = "udp"
cidr_blocks = ["0.0.0.0/0"] # Restrict this
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "swarm-nodes-sg"
}
}
resource "aws_instance" "swarm_manager" {
ami = "ami-0abcdef1234567890" # Replace with a current AMI ID
instance_type = "t3.medium"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.swarm_nodes.id]
key_name = "your-ssh-key" # Replace with your key pair name
user_data = <<-EOF
#!/bin/bash
# Install Docker and Docker Swarm
apt-get update -y
apt-get install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
apt-get update -y
apt-get install -y docker-ce docker-ce-cli containerd.io
usermod -aG docker ubuntu
# Initialize Swarm (adjust IP)
docker swarm init --advertise-addr ${self.private_ip}
EOF
tags = {
Name = "swarm-manager-01"
Role = "manager"
}
}
# Add similar resources for worker nodes, joining the swarm using the token from the manager
Terraform Configuration Snippet (AWS ALB)
And here's how you might define the ALB, target group, and listener:
resource "aws_security_group" "alb" {
name = "alb-sg"
description = "Allow HTTP/HTTPS from anywhere"
vpc_id = aws_vpc.main.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "wordpress-alb-sg"
}
}
resource "aws_lb" "wordpress_alb" {
name = "wordpress-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = aws_subnet.public.ids # Use multiple subnets for HA
tags = {
Name = "wordpress-alb"
}
}
resource "aws_lb_target_group" "wordpress_tg" {
name = "wordpress-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
health_check {
path = "/wp-cron.php" # Or a custom health check endpoint
protocol = "HTTP"
matcher = "200"
interval = 30
timeout = 5
healthy_threshold = 3
unhealthy_threshold = 3
}
tags = {
Name = "wordpress-tg"
}
}
resource "aws_lb_listener" "wordpress_http_listener" {
load_balancer_arn = aws_lb.wordpress_alb.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.wordpress_tg.arn
}
}
# Add HTTPS listener with ACM certificate for production
# resource "aws_lb_listener" "wordpress_https_listener" { ... }
Orchestration and Management
With Docker Swarm managing the application containers and AWS ALB handling external traffic, you have a robust, highly available WordPress setup. Key management aspects include:
- Scaling: Adjust the
replicascount in yourdocker-compose.ymlfor WordPress and PHP-FPM services. Swarm will automatically provision new containers. - Updates: Use
docker stack deploywith rolling updates. Theupdate_configin the compose file controls the rollout strategy. - Monitoring: Implement monitoring for your Swarm nodes (CPU, memory, disk) and application health (via ALB health checks and application-level metrics). Consider tools like Prometheus and Grafana.
- Logging: Centralize logs from your Docker containers using a logging driver (e.g., `awslogs` to CloudWatch Logs) or a dedicated logging solution like ELK stack.
- Database Backups: Regularly back up your MariaDB data volume. For production, AWS RDS with automated backups is highly recommended.
Considerations for Production
While this setup provides high availability, several points are critical for a production environment:
- Managed Database: Replace the self-hosted MariaDB with AWS RDS for managed backups, replication, and failover.
- Persistent Storage: For database data, use AWS EBS volumes managed by Terraform for better performance and reliability than Docker volumes on EC2.
- Secrets Management: Use AWS Secrets Manager or HashiCorp Vault for managing database credentials and other sensitive information, rather than environment variables or Docker Secrets directly.
- CI/CD Pipeline: Automate the build, test, and deployment process using a CI/CD tool (e.g., Jenkins, GitLab CI, AWS CodePipeline).
- CDN: Integrate with a Content Delivery Network (CDN) like AWS CloudFront for caching static assets closer to users, reducing load on your Swarm nodes.
- WAF: Deploy AWS WAF with your ALB to protect against common web exploits.