Dockerizing and Orchestrating Legacy WordPress Systems on Modern DigitalOcean Infrastructure
Containerizing WordPress: The Foundation
Migrating legacy WordPress installations to a containerized environment offers significant advantages in terms of consistency, portability, and scalability. This section details the process of creating Docker images for WordPress and its dependencies, focusing on a production-ready setup.
Dockerfile for WordPress Core and PHP-FPM
We’ll start by crafting a Dockerfile that bundles WordPress core with PHP-FPM. This approach decouples the web server from the PHP execution, a common and robust pattern.
Consider a scenario where you need to manage custom plugins and themes. We’ll incorporate these directly into the image for simplicity, though for more dynamic deployments, volume mounts or separate image layers are preferable.
# Use an official PHP image with FPM and Apache (for serving static assets)
FROM php:8.1-fpm-apache
# Set working directory
WORKDIR /var/www/html
# Install necessary PHP extensions
RUN docker-php-ext-install pdo pdo_mysql zip exif gd && docker-php-ext-enable gd
# Install WordPress
RUN curl -o wordpress.tar.gz -SL https://wordpress.org/latest.tar.gz \
&& tar -xzf wordpress.tar.gz --strip-components=1 \
&& rm wordpress.tar.gz
# Copy custom plugins and themes (adjust paths as needed)
COPY ./wp-content/plugins/ /var/www/html/wp-content/plugins/
COPY ./wp-content/themes/ /var/www/html/wp-content/themes/
# Configure Apache to serve WordPress correctly
# Remove default Apache index.html
RUN rm -rf /var/www/html/index.html
# Enable Apache rewrite module for permalinks
RUN a2enmod rewrite
# Set correct permissions for WordPress files
RUN chown -R www-data:www-data /var/www/html
# Expose port 80 for Apache
EXPOSE 80
# Start Apache in foreground
CMD ["apache2-foreground"]
Dockerfile for MySQL Database
A separate container for the MySQL database is essential for maintainability and data management. We’ll use the official MySQL image and configure it for WordPress.
# Use an official MySQL image FROM mysql:8.0 # Set environment variables for database initialization ENV MYSQL_ROOT_PASSWORD=your_root_password ENV MYSQL_DATABASE=wordpress_db ENV MYSQL_USER=wordpress_user ENV MYSQL_PASSWORD=your_wordpress_password # Copy custom MySQL configuration if needed # COPY ./my.cnf /etc/mysql/conf.d/my.cnf # Expose MySQL port EXPOSE 3306
Docker Compose for Orchestration
Docker Compose simplifies the management of multi-container Docker applications. It allows us to define and run our WordPress and MySQL services in a single command.
version: '3.8'
services:
wordpress:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:80"
volumes:
- wordpress_data:/var/www/html
depends_on:
- db
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_NAME: wordpress_db
WORDPRESS_DB_USER: wordpress_user
WORDPRESS_DB_PASSWORD: your_wordpress_password
networks:
- wordpress_network
db:
build:
context: .
dockerfile: Dockerfile.db # Assuming Dockerfile.db for MySQL
ports:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: your_root_password
MYSQL_DATABASE: wordpress_db
MYSQL_USER: wordpress_user
MYSQL_PASSWORD: your_wordpress_password
networks:
- wordpress_network
volumes:
wordpress_data:
db_data:
networks:
wordpress_network:
Note: In the docker-compose.yml, I’ve assumed a separate Dockerfile.db for the MySQL service. You would adapt the MySQL Dockerfile above to be named Dockerfile.db and place it in the same directory as your docker-compose.yml. Also, ensure the environment variables in docker-compose.yml match those in your MySQL Dockerfile.
DigitalOcean Droplet Setup and Configuration
For deploying this containerized WordPress on DigitalOcean, we’ll use a standard Droplet. The key is to install Docker and Docker Compose on the Droplet and then deploy our application.
Provisioning a DigitalOcean Droplet
Create a new Droplet on DigitalOcean. A general-purpose Droplet (e.g., 2 vCPU, 4GB RAM) should suffice for a moderately trafficked WordPress site. Choose an Ubuntu 22.04 LTS image for a stable environment.
Installing Docker and Docker Compose
Connect to your Droplet via SSH and execute the following commands to install Docker and Docker Compose:
# Update package list sudo apt update sudo apt upgrade -y # Install Docker sudo apt install -y apt-transport-https ca-certificates curl software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io # Add current user to the docker group to run docker commands without sudo sudo usermod -aG docker $USER newgrp docker # Apply group changes to the current session # Install Docker Compose sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose # Verify installations docker --version docker-compose --version
Deploying the WordPress Application
Transfer your Dockerfiles and docker-compose.yml to the Droplet. A common practice is to create a dedicated directory for your application, e.g., ~/wordpress-docker.
# Create application directory mkdir ~/wordpress-docker cd ~/wordpress-docker # Upload your Dockerfiles and docker-compose.yml here using scp or other methods # Build and start the containers docker-compose up -d
This command will build the Docker images (if not already present) and start the WordPress and MySQL containers in detached mode. You can monitor the logs using docker-compose logs -f.
Production Considerations: Nginx as a Reverse Proxy
While the Apache server within the WordPress container can serve requests, for production environments, it’s highly recommended to use a dedicated reverse proxy like Nginx. This offers better performance, SSL termination, and load balancing capabilities.
Nginx Configuration for WordPress
We’ll run Nginx in its own container, forwarding requests to the WordPress container’s port 80.
# Dockerfile for Nginx Reverse Proxy FROM nginx:alpine # Remove default Nginx configuration RUN rm /etc/nginx/conf.d/default.conf # Copy custom Nginx configuration COPY nginx.conf /etc/nginx/conf.d/default.conf # Expose port 80 and 443 (for SSL) EXPOSE 80 EXPOSE 443
# nginx.conf
server {
listen 80;
server_name your_domain.com; # Replace with your domain
# Redirect HTTP to HTTPS (if SSL is configured)
# location / {
# return 301 https://$host$request_uri;
# }
location / {
proxy_pass http://wordpress:80; # 'wordpress' is the service name in docker-compose.yml
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
}
# Serve static assets directly from Nginx for performance
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
root /var/www/html/wp-content; # Adjust path if your WP content is elsewhere
expires 30d;
access_log off;
}
}
# Add SSL configuration here if needed
# server {
# listen 443 ssl;
# server_name your_domain.com;
#
# ssl_certificate /etc/letsencrypt/live/your_domain.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/your_domain.com/privkey.pem;
#
# # ... other SSL settings ...
#
# location / {
# proxy_pass http://wordpress:80;
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_redirect off;
# }
# }
Update your docker-compose.yml to include the Nginx service:
version: '3.8'
services:
nginx:
build:
context: .
dockerfile: Dockerfile.nginx # Assuming Dockerfile.nginx for Nginx
ports:
- "80:80"
- "443:443" # If SSL is configured
depends_on:
- wordpress
networks:
- wordpress_network
wordpress:
build:
context: .
dockerfile: Dockerfile
volumes:
- wordpress_data:/var/www/html
depends_on:
- db
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_NAME: wordpress_db
WORDPRESS_DB_USER: wordpress_user
WORDPRESS_DB_PASSWORD: your_wordpress_password
networks:
- wordpress_network
db:
build:
context: .
dockerfile: Dockerfile.db
ports:
- "3306:3306"
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: your_root_password
MYSQL_DATABASE: wordpress_db
MYSQL_USER: wordpress_user
MYSQL_PASSWORD: your_wordpress_password
networks:
- wordpress_network
volumes:
wordpress_data:
db_data:
networks:
wordpress_network:
Persistent Storage and Backups
The use of Docker volumes (wordpress_data and db_data) ensures that your WordPress files and MySQL data persist even if containers are recreated. For production, consider using DigitalOcean’s managed databases (like DigitalOcean Managed Databases for MySQL) or dedicated block storage for more robust data management and backup solutions.
Regular backups of both the WordPress files (from the wordpress_data volume) and the MySQL database are critical. You can automate this using cron jobs on the host Droplet that interact with the Docker volumes or by leveraging DigitalOcean’s snapshot features.
Scaling and High Availability
For higher traffic or improved availability, you can scale your WordPress deployment. This typically involves:
- Multiple WordPress Instances: Run multiple instances of the WordPress container behind a load balancer (e.g., DigitalOcean Load Balancer or HAProxy).
- Shared Storage: Use a shared network filesystem (like NFS) or object storage for WordPress uploads if multiple WordPress instances need to access them.
- Database Replication: For read-heavy workloads, consider MySQL replication.
- Container Orchestration: For more complex scaling and management, explore Kubernetes (e.g., DigitalOcean Kubernetes) or Docker Swarm.
When scaling WordPress instances, ensure that the WORDPRESS_DB_HOST environment variable in your docker-compose.yml points to the correct database service. If using a managed database, you’ll use its connection string instead of the Docker service name.