• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Dockerizing and Orchestrating Legacy WordPress Systems on Modern Linode Infrastructure

Dockerizing and Orchestrating Legacy WordPress Systems on Modern Linode Infrastructure

Assessing Legacy WordPress for Containerization

Before embarking on the containerization journey for a legacy WordPress installation, a thorough assessment is paramount. This involves identifying dependencies, understanding the existing infrastructure, and evaluating the potential benefits and challenges. Legacy systems often have tightly coupled components, custom PHP versions, or specific server configurations that need careful consideration. We’ll focus on a common scenario: a WordPress site with a MySQL database and potentially some custom PHP plugins or themes that might rely on specific PHP extensions or configurations.

Crafting the Dockerfile for WordPress and MySQL

We’ll start by defining our Docker environment. A multi-stage build is beneficial for optimizing the final image size and security. For this example, we’ll use official WordPress and MySQL images as our base, layering our customizations on top.

First, let’s define the WordPress Dockerfile. This will handle the WordPress core, themes, and plugins. We’ll assume a need for specific PHP extensions and a custom `wp-config.php`.

# Stage 1: Builder
FROM php:8.2-fpm-alpine AS builder

# Install necessary PHP extensions for WordPress and common plugins
RUN apk update && apk add --no-cache \
    libzip-dev \
    libpng-dev \
    libjpeg-turbo-dev \
    freetype-dev \
    icu-dev \
    imagemagick-dev \
    git \
    zip \
    unzip \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) gd zip exif \
    && pecl install imagick \
    && docker-php-ext-enable imagick \
    && apk del libzip-dev libpng-dev libjpeg-turbo-dev freetype-dev icu-dev imagemagick-dev git

# Stage 2: Production
FROM php:8.2-fpm-alpine

# Copy installed extensions from builder stage
COPY --from=builder /usr/local/lib/php/extensions/no-debug-non-zts-20220829/ /usr/local/lib/php/extensions/no-debug-non-zts-20220829/

# Install runtime dependencies
RUN apk update && apk add --no-cache \
    libzip \
    libpng \
    libjpeg-turbo \
    freetype \
    icu \
    imagemagick \
    ghostscript \
    tzdata \
    && docker-php-ext-enable gd imagick zip exif

# Set timezone
ENV TZ=UTC

# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer

# Set working directory
WORKDIR /var/www/html

# Download WordPress core
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 wp-config.php (if any)
# COPY wp-config.php /var/www/html/wp-config.php

# Copy custom themes and plugins (if needed)
# COPY wp-content/themes/ /var/www/html/wp-content/themes/
# COPY wp-content/plugins/ /var/www/html/wp-content/plugins/

# Ensure correct permissions
RUN chown -R www-data:www-data /var/www/html && \
    chmod -R 755 /var/www/html

# Expose port
EXPOSE 9000

# Default command to run PHP-FPM
CMD ["php-fpm"]

Next, the `docker-compose.yml` file orchestrates the WordPress and MySQL services. This configuration defines the services, their images, ports, volumes, and network settings. We’ll use persistent volumes for MySQL data and WordPress uploads to ensure data durability.

version: '3.8'

services:
  db:
    image: mysql:8.0
    container_name: wordpress_db
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress_user
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    networks:
      - wordpress_network

  wordpress:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: wordpress_app
    ports:
      - "80:80"
    volumes:
      - wp_content:/var/www/html/wp-content
      - wp_uploads:/var/www/html/wp-content/uploads
    restart: always
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress_user
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
    depends_on:
      - db
    networks:
      - wordpress_network

volumes:
  db_data:
  wp_content:
  wp_uploads:

networks:
  wordpress_network:
    driver: bridge

To manage environment variables like database passwords, create a `.env` file in the same directory as your `docker-compose.yml`:

MYSQL_ROOT_PASSWORD=your_strong_root_password
MYSQL_PASSWORD=your_strong_wordpress_password

Deploying to Linode Kubernetes Engine (LKE)

Linode Kubernetes Engine (LKE) provides a managed Kubernetes experience, simplifying cluster management. We’ll translate our `docker-compose` setup into Kubernetes manifests.

First, ensure you have `kubectl` configured to communicate with your LKE cluster. You can download your cluster’s kubeconfig file from the Linode Cloud Manager.

Kubernetes Deployment for MySQL

This deployment defines the MySQL stateful application. Using a `StatefulSet` is crucial for databases as it provides stable network identifiers and persistent storage.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: wordpress-db
  labels:
    app: wordpress-db
spec:
  serviceName: wordpress-db
  replicas: 1
  selector:
    matchLabels:
      app: wordpress-db
  template:
    metadata:
      labels:
        app: wordpress-db
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
          name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secrets
              key: root-password
        - name: MYSQL_DATABASE
          value: "wordpress"
        - name: MYSQL_USER
          value: "wordpress_user"
        - name: MYSQL_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secrets
              key: password
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: mysql-persistent-storage
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 10Gi # Adjust storage size as needed
      storageClassName: linode-block-storage # Or your preferred Linode storage class

Kubernetes Deployment for WordPress

The WordPress deployment uses a `Deployment` for the application pods and a `Service` to expose it. We’ll use `PersistentVolumeClaims` for `wp-content` and `wp-uploads` to ensure data persistence.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress-app
  labels:
    app: wordpress-app
spec:
  replicas: 2 # Scale WordPress pods for high availability
  selector:
    matchLabels:
      app: wordpress-app
  template:
    metadata:
      labels:
        app: wordpress-app
    spec:
      containers:
      - name: wordpress
        image: your-dockerhub-username/wordpress-legacy:latest # Replace with your image
        ports:
        - containerPort: 80
        env:
        - name: WORDPRESS_DB_HOST
          value: "wordpress-db-0.wordpress-db.default.svc.cluster.local:3306" # Service name for MySQL
        - name: WORDPRESS_DB_NAME
          value: "wordpress"
        - name: WORDPRESS_DB_USER
          value: "wordpress_user"
        - name: WORDPRESS_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secrets
              key: password
        volumeMounts:
        - name: wp-content-volume
          mountPath: /var/www/html/wp-content
        - name: wp-uploads-volume
          mountPath: /var/www/html/wp-content/uploads
      volumes:
      - name: wp-content-volume
        persistentVolumeClaim:
          claimName: wp-content-pvc
      - name: wp-uploads-volume
        persistentVolumeClaim:
          claimName: wp-uploads-pvc

---
apiVersion: v1
kind: Service
metadata:
  name: wordpress-app-service
spec:
  selector:
    app: wordpress-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer # Use LoadBalancer for external access

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-content-pvc
spec:
  accessModes: [ "ReadWriteOnce" ]
  resources:
    requests:
      storage: 5Gi # Adjust storage size as needed
  storageClassName: linode-block-storage # Or your preferred Linode storage class

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-uploads-pvc
spec:
  accessModes: [ "ReadWriteOnce" ]
  resources:
    requests:
      storage: 10Gi # Adjust storage size as needed
  storageClassName: linode-block-storage # Or your preferred Linode storage class

Kubernetes Secrets for Credentials

It’s critical to manage sensitive information like database passwords securely. Kubernetes Secrets are the standard way to do this.

apiVersion: v1
kind: Secret
metadata:
  name: mysql-secrets
type: Opaque
data:
  root-password: ${MYSQL_ROOT_PASSWORD} # Base64 encoded
  password: ${MYSQL_PASSWORD} # Base64 encoded

Note: You’ll need to base64 encode your passwords before putting them into the `data` section of the Secret manifest. For example, on Linux/macOS:

echo -n 'your_strong_root_password' | base64
echo -n 'your_strong_wordpress_password' | base64

Nginx Ingress Controller for Advanced Routing

For production environments, an Ingress controller is essential for managing external access to services, SSL termination, and advanced routing rules. We’ll deploy the Nginx Ingress Controller and configure an Ingress resource for our WordPress application.

Deploying Nginx Ingress Controller

You can install the Nginx Ingress Controller using Helm or by applying its YAML manifests directly. Using Helm is generally recommended for easier management and upgrades.

# Add the ingress-nginx Helm repository
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

# Install the ingress-nginx controller
helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace \
  --set controller.replicaCount=2 \
  --set controller.nodeSelector."kubernetes\.io/os"=linux \
  --set defaultBackend.nodeSelector."kubernetes\.io/os"=linux

After installation, obtain the external IP address of the Ingress controller. This will be the public IP for your WordPress site.

kubectl get svc -n ingress-nginx

Configuring the WordPress Ingress Resource

This Ingress resource directs external traffic to the `wordpress-app-service`.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: wordpress-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    # Add other Nginx specific annotations as needed, e.g., for SSL
spec:
  ingressClassName: nginx # Ensure this matches your Ingress Controller's class
  rules:
  - host: your-domain.com # Replace with your actual domain
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: wordpress-app-service
            port:
              number: 80

Migration and Post-Deployment Considerations

Migrating a live legacy WordPress site requires careful planning to minimize downtime. This typically involves a database dump and restore, followed by file synchronization.

Database Migration Strategy

1. Downtime Window: Announce a maintenance window for the migration.

2. Database Dump: On the legacy server, create a dump of the WordPress database.

mysqldump -u [legacy_db_user] -p [legacy_db_name] > wordpress_backup.sql

3. Transfer Dump: Securely transfer `wordpress_backup.sql` to a location accessible by your LKE cluster (e.g., a temporary pod or your local machine).

4. Import into LKE MySQL: Use `kubectl exec` to run the import command within the MySQL pod.

# First, ensure your mysql pod is running and get its name
kubectl get pods -l app=wordpress-db

# Then, execute the import command
kubectl exec -it wordpress-db-0 -- mysql -u root -p"${MYSQL_ROOT_PASSWORD}" wordpress < wordpress_backup.sql

File Synchronization

Synchronize the `wp-content` directory (especially `uploads`) from the legacy server to the persistent volumes in LKE. This can be done using `rsync`.

# On your local machine or a bastion host
rsync -avz --progress /path/to/legacy/wp-content/uploads/ user@your-linode-ip:/path/to/your/wp-uploads/volume/

You’ll need to identify the exact mount path for your `wp-uploads` volume within the WordPress pod. You can find this by inspecting the pod definition or by exec-ing into the pod and checking.

DNS Update

Once the data is migrated and verified, update your domain’s DNS records to point to the external IP address of your Nginx Ingress Controller service.

Monitoring and Maintenance

Post-deployment, robust monitoring is crucial. Utilize Linode’s monitoring tools, Prometheus, and Grafana for cluster and application-level metrics. Regularly check logs using `kubectl logs` and consider a centralized logging solution like ELK stack or Loki.

Regularly update your Docker images, Kubernetes manifests, and Helm charts to incorporate security patches and new features. Automate these processes using CI/CD pipelines for seamless deployments.

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability
  • Scala Pekko vs. Go Goroutines: Actor Model vs. CSP for Event-Driven Reactive Systems
  • Java Loom Virtual Threads vs. Go Goroutines: Under-the-Hood Scheduler and Thread Overhead Comparison

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (584)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (806)
  • PHP (5)
  • PHP Development (21)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (19)
  • Ruby on Rails (1)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (7)
  • WordPress Theme Development (357)

Recent Posts

  • Go Goroutines vs. Node.js Event Loop: Scaling I/O-Bound Microservices Under High Load
  • Elixir Phoenix vs. Go Gin: Concurrency Models and Fault Tolerance Under Peak Request Volume
  • Python Celery vs. Go Channels: Distributed Task Queue Overhead and Memory Reliability

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (806)
  • Debugging & Troubleshooting (584)
  • Security & Compliance (543)
  • SEO & Growth (491)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala