Understanding Docker and Containerising WordPress for Scalability (Part 2)


Denny Lim

Denny Lim

Β·July 3, 2025Β·
DockerWordpress

This guide is ideal for you if:

  • You currently have a single-server setup running WordPress with PHP, MySQL, and Nginx.
  • Your server struggles to handle traffic increases or fluctuations during peak hours or campaign periods.
  • You want to containerise your WordPress application to achieve better scalability.

This is a two-part tutorial:

  • Part 1: Installing WordPress on Ubuntu 24.04 with Nginx and MySQL.
  • Part 2: Understanding Docker and Containerising WordPress πŸ‘ˆ You are here

By the end of this tutorial series, you’ll understand the key components powering your WordPress website and learn the core concepts and fundamentals of containerising your applications.

The source code for this guide is available on GitHub .


Understanding Docker and Containerising WordPress

Key Components for Powering a WordPress Site

  • Web Server (Nginx, Apache, or LiteSpeed) – Handles HTTP requests.
  • PHP (v7.4 or higher recommended) – Processes WordPress dynamically.
  • Database Server (MySQL or MariaDB) – Stores all site data.

The components listed above need to be broken downβ€”meaning we will separate them from our single-instance server to achieve scalability, enhanced security, improved performance, and greater cost efficiency.

Scalability

  • Independent Scaling:
    • Vertical Scaling: Upgrade the database server’s RAM/CPU without affecting the web server.
    • Horizontal Scaling: Add read replicas for MySQL to handle high traffic, while WordPress scales separately.
  • Load Balancing: WordPress can run on multiple servers (containers) connecting to the same centralised MySQL database.

Enhanced Security

  • Reduced Attack Surface: A single-server setup exposes the database to web-facing threats (e.g., SQL injection). Isolating the database limits direct access.
  • Network Segmentation: The database can be placed in a private subnet, accessible only by the app server (e.g., via firewall rules or VPC peering).

Improved Performance

  • Resource Isolation: MySQL can consume significant CPU, RAM, and I/O. Running it on a separate server prevents competition with WordPress/PHP for resources.
  • Optimised Hardware: Databases benefit from fast storage (SSDs/NVMe), while web servers require more CPU/RAM. Dedicated database servers can be tuned specifically for database workloads.
  • Reduced Latency: If the database is overloaded, WordPress performance suffers. Separation ensures queries don’t slow down page rendering.

Cost Efficiency

  • Optimised Resource Allocation: Avoid overprovisioning a single large server. Smaller, purpose-built servers can be more cost-effective.
  • Cloud Savings: In cloud environments (AWS RDS, Google Cloud SQL), managed database services often offer auto-scaling and pay-as-you-go pricing.

Containerisation Step 1 – Break Down Components

Identify and split your application into logical services (e.g., frontend, backend, database). In our WordPress setup, we have:

  • Web (Nginx)
  • WordPress (PHP-FPM)
  • Database (MySQL)

Containerisation Step 2 – Plan the Networking: How Containers Communicate

Containers need to communicate with:

  • Each other (inter-service communication, e.g., Web β†’ WordPress β†’ Database).
  • External services (APIs, cloud databases).
  • End users (via public-facing ports).

Containerisation Step 3 – Plan the Storage: Ephemeral vs. Persistent

Ephemeral Storage (Stateless Apps)

  • Data exists only while the container runs (deleted when the container stops).
  • Use cases: Web servers (Nginx), APIs, caches (Redis).

Persistent Storage (Stateful Apps)

  • Data persists beyond container restarts/deployments.
  • Use cases: Databases (MySQL), file uploads, user sessions.

Containerisation Step 4 – Dockerise the Application

  • Create a Dockerfile for each service or use a pre-built image.
  • Define dependencies, ports, and environment variables.

File Structure

wordpress-docker/ β”œβ”€β”€ docker-compose.yml # Orchestration β”œβ”€β”€ nginx/ β”‚ └── nginx.conf # Custom Nginx config β”œβ”€β”€ mysql/ β”‚ └── my.cnf # MySQL tuning └── wordpress/ └── wp-config.php # Custom WordPress config

File: docker-compose.yml

services: # Database (MySQL) mysql: image: mysql:8.0 container_name: wp-db volumes: - mysql_data:/var/lib/mysql # Persistent storage - ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf networks: - wp-network environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: wpdb MYSQL_USER: wpuser MYSQL_PASSWORD: wppassword ports: # MySQL port (forward if needed for tools like MySQL Workbench) - "3306" # WordPress (PHP-FPM) wordpress: image: wordpress:6.8.1-php8.2-fpm-alpine container_name: wp-app depends_on: - mysql volumes: - wordpress_data:/var/www/html # Persistent storage networks: - wp-network environment: WORDPRESS_DB_HOST: mysql WORDPRESS_DB_USER: wpuser WORDPRESS_DB_PASSWORD: wppassword WORDPRESS_DB_NAME: wpdb ports: - "9000" # PHP-FPM port # Web (Nginx) nginx: image: nginx:alpine container_name: wp-nginx volumes: - wordpress_data:/usr/share/nginx/html - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf networks: - wp-network ports: - "8080:80" # Host machine port depends_on: - wordpress # Define networks and volumes networks: wp-network: driver: bridge volumes: wordpress_data: # WordPress files driver: local mysql_data: # Database files driver: local

Containerisation Step 5 – Test Locally Using Docker Compose

Start the containers:

docker compose up -d

The -d flag runs containers in detached mode (background).

Expected output:

βœ” Network wordpress-docker_wp-network Created βœ” Volume "wordpress-docker_wordpress_data" Created βœ” Volume "wordpress-docker_mysql_data" Created βœ” Container wp-db Started βœ” Container wp-app Started βœ” Container wp-nginx Started

Navigate to http://localhost:8080 and complete the WordPress installation. Create sample blog posts and upload images to test persistent storage.

Stop the containers:

docker compose down

Expected output:

βœ” Container wp-nginx Removed βœ” Container wp-app Removed βœ” Container wp-db Removed βœ” Network wordpress-docker_wp-network Removed

Start the containers:

docker compose up -d

Navigate to http://localhost:8080, your previous posts and uploaded images should be present.

Start a fresh installation (delete persistent volumes):

To remove all containers and volumes, run:

docker compose down -v

Warning: The -v flag removes all volumes. Avoid this in production to prevent data loss.

Expected output:

βœ” Container wp-nginx Removed βœ” Container wp-app Removed βœ” Container wp-db Removed βœ” Network wordpress-docker_wp-network Removed βœ” Volume wordpress-docker_mysql_data Removed βœ” Volume wordpress-docker_wordpress_data Removed

Restart the containers:

docker compose up -d

Again, navigate to http://localhost:8080, WordPress will prompt for a fresh installation.


Containerisation Step 6 – Deploying to Production

Deploy containers using Docker Compose or Kubernetes (K8s), depending on requirements. This guide uses DigitalOcean Managed Kubernetes (DOKS).

Important Note: This tutorial uses the wordpress namespace. To change it, either edit all .yaml files in the deploy folder, or run the following command in terminal.

find ./deploy -type f \( -name "*.yaml" \) -exec sed -i.bak "s/namespace: wordpress/namespace: your-preferred-namespace/g" {} \;

Replace your-preferred-namespace with your desired namespace.

Deployment Steps

Apply the .yaml files in the deploy folder (modify as needed).

Deploy MySQL Database (Optional)

Skip this step if using a managed MySQL service. Update WORDPRESS_DB_HOST, WORDPRESS_DB_USER, WORDPRESS_DB_PASSWORD, and WORDPRESS_DB_NAME in ./deploy/02-wordpress.yaml accordingly.

kubectl apply -f ./deploy/01-mysql.yaml

Deploy WordPress Engine

kubectl apply -f ./deploy/02-wordpress.yaml

Deploy Nginx

kubectl apply -f ./deploy/03-nginx.yaml

Deploy Ingress

kubectl apply -f ./deploy/04-ingress.yaml