• 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 » Leveraging Docker Compose and Kubernetes for Scalable, Zero-Downtime WordPress Headless Deployments

Leveraging Docker Compose and Kubernetes for Scalable, Zero-Downtime WordPress Headless Deployments

Decoupling WordPress: The Headless Architecture

Traditional WordPress deployments, while robust for content management, often present challenges when aiming for high scalability, granular control over the frontend, and seamless integration with modern JavaScript frameworks. Adopting a headless architecture, where WordPress serves solely as a backend content API and a separate frontend application consumes this API, is the key to unlocking these benefits. This approach allows us to leverage the strengths of WordPress for content creation and management while empowering developers with the flexibility of modern frontend technologies and robust deployment strategies.

Local Development with Docker Compose

Before diving into production-grade Kubernetes deployments, a solid local development environment is crucial. Docker Compose provides an excellent way to define and run multi-container Docker applications. For a headless WordPress setup, this typically involves at least two services: the WordPress application itself (often with a PHP-FPM backend) and a database (e.g., MySQL or MariaDB).

Let’s define a docker-compose.yml file for a basic headless WordPress development environment. This setup will include WordPress, a MySQL database, and an Nginx reverse proxy to serve static assets and potentially handle API requests. For a true headless setup, we’ll also include a placeholder for our frontend application.

docker-compose.yml Configuration

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:-rootpassword}
      MYSQL_DATABASE: ${MYSQL_DATABASE:-wordpress}
      MYSQL_USER: ${MYSQL_USER:-wordpressuser}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD:-wordpresspassword}
    networks:
      - wordpress_network

  wordpress:
    build:
      context: ./wordpress
      dockerfile: Dockerfile
    container_name: wordpress_app
    depends_on:
      - db
    ports:
      - "8000:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: ${MYSQL_USER:-wordpressuser}
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD:-wordpresspassword}
      WORDPRESS_DB_NAME: ${MYSQL_DATABASE:-wordpress}
      WORDPRESS_TABLE_PREFIX: wp_
      WP_HOME: http://localhost:8000
      WP_SITEURL: http://localhost:8000/wp
    volumes:
      - ./wordpress/html:/var/www/html
    restart: always
    networks:
      - wordpress_network

  # Placeholder for your headless frontend application
  # For example, a React or Vue.js app served by Nginx
  frontend:
    image: nginx:alpine
    container_name: frontend_app
    ports:
      - "3000:80"
    volumes:
      - ./frontend/build:/usr/share/nginx/html
      - ./frontend/nginx.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - wordpress
    restart: always
    networks:
      - wordpress_network

volumes:
  db_data:

networks:
  wordpress_network:
    driver: bridge

In this configuration:

  • db: A MySQL 8.0 instance to store WordPress content. Environment variables are used for credentials, making them configurable via a .env file.
  • wordpress: This service will build from a local Dockerfile (detailed below) and is configured to connect to the db service. It exposes port 8000 locally.
  • frontend: A placeholder for your headless frontend. It’s served by Nginx and expects a build directory and an nginx.conf file.
  • volumes: Persistent storage for the database data.
  • networks: A custom bridge network to allow containers to communicate with each other using service names.

WordPress Dockerfile

Create a wordpress/Dockerfile with the following content. This Dockerfile will install necessary PHP extensions and configure Nginx for WordPress.

FROM php:8.2-fpm-alpine

LABEL maintainer="Antigravity <[email protected]>"

# Install system dependencies
RUN apk update && apk upgrade && \
    apk add --no-cache \
    nginx \
    mysql-client \
    libzip-dev \
    libpng-dev \
    libjpeg-turbo-dev \
    freetype-dev \
    icu-dev \
    imagemagick \
    imagemagick-dev \
    git \
    curl \
    unzip \
    supervisor

# Install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) gd \
    && docker-php-ext-install pdo pdo_mysql zip exif pcntl bcmath intl opcache

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

# Configure Nginx
COPY wordpress/nginx.conf /etc/nginx/sites-available/default
RUN ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default

# Configure PHP-FPM
COPY wordpress/php-fpm.conf /etc/php/8.2/fpm/php-fpm.conf
COPY wordpress/php.ini /etc/php/8.2/fpm/conf.d/99-custom.ini

# Copy WordPress core files (or use a volume for easier updates)
# For production, consider a multi-stage build to copy only necessary files
COPY --chown=www-data:www-data https://wordpress.org/latest.zip /tmp/wordpress.zip
RUN unzip /tmp/wordpress.zip -d /var/www/html/ && \
    chown -R www-data:www-data /var/www/html/wordpress && \
    mv /var/www/html/wordpress/* /var/www/html/ && \
    rmdir /var/www/html/wordpress && \
    rm /tmp/wordpress.zip

# Copy custom WordPress configurations and themes/plugins if needed
# COPY wordpress/wp-config.php /var/www/html/wp-config.php
# COPY wordpress/wp-content/themes /var/www/html/wp-content/themes
# COPY wordpress/wp-content/plugins /var/www/html/wp-content/plugins

# Configure Supervisor for Nginx and PHP-FPM
COPY wordpress/supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# Expose ports
EXPOSE 80

# Start Supervisor
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]

WordPress Nginx Configuration

server {
    listen 80;
    index index.php index.html index.htm;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/html;

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 1y;
        log_not_found off;
    }

    # Deny access to sensitive files
    location ~* wp-config\.php { deny all; }
    location ~ /\. { deny all; }
}

PHP-FPM Configuration

[global]
pid = /run/php/php8.2-fpm.pid
error_log = /var/log/php-fpm/error.log
log_level = notice

[www]
user = www-data
group = www-data
listen = /run/php/php8.2-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 50
pm.min_spare_servers = 5
pm.max_spare_servers = 10
pm.start_servers = 2
pm.slowlog_timeout = 30s
request_terminate_timeout = 120s
request_slowlog_timeout = 60s
catch_workers_output = yes

Supervisor Configuration

[supervisord]
nodaemon=true
user=root

[program:nginx]
command=/usr/sbin/nginx -g "daemon off;"
autostart=true
autorestart=true
priority=10
stdout_logfile=/var/log/supervisor/nginx_stdout.log
stderr_logfile=/var/log/supervisor/nginx_stderr.log

[program:php-fpm]
command=/usr/local/sbin/php-fpm --nodaemonize --fpm-config /etc/php/8.2/fpm/php-fpm.conf
autostart=true
autorestart=true
priority=20
stdout_logfile=/var/log/supervisor/php-fpm_stdout.log
stderr_logfile=/var/log/supervisor/php-fpm_stderr.log

To start your local development environment, create a .env file in the root of your project with your desired database credentials (or use the defaults):

MYSQL_ROOT_PASSWORD=my_root_password
MYSQL_DATABASE=my_wp_db
MYSQL_USER=my_wp_user
MYSQL_PASSWORD=my_wp_password

Then, run:

docker-compose up -d

You can now access your WordPress instance at http://localhost:8000 and your frontend at http://localhost:3000. Remember to configure your frontend application to fetch data from the WordPress REST API (e.g., http://localhost:8000/wp-json/wp/v2/posts).

Kubernetes Deployment Strategy

Transitioning to Kubernetes requires a more sophisticated approach to manage scalability, resilience, and zero-downtime deployments. We’ll leverage Kubernetes Deployments for our WordPress application and database, Services for network access, and potentially Ingress for external traffic management. For zero-downtime updates, we’ll focus on Kubernetes’ rolling update strategy.

Kubernetes Manifests: WordPress and Database

We’ll define Kubernetes resources using YAML manifests. For simplicity, we’ll use a managed database service (like AWS RDS, Google Cloud SQL, or Azure Database for MySQL) in production, but for demonstration, we’ll include a StatefulSet for MySQL. In a real-world production scenario, you would typically use a managed database service and configure your WordPress deployment to connect to it via environment variables or secrets.

MySQL StatefulSet and Service

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  serviceName: mysql
  replicas: 1 # For production, consider a managed DB service
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    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: wordpressuser
        - 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 as needed
---
apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  ports:
  - port: 3306
    targetPort: 3306
  selector:
    app: mysql
  clusterIP: None # Headless service for StatefulSet

WordPress Deployment and Service

apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  replicas: 3 # Scale as needed
  selector:
    matchLabels:
      app: wordpress
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1 # Allow one pod to be unavailable during update
      maxSurge: 1       # Allow one extra pod to be created during update
  template:
    metadata:
      labels:
        app: wordpress
    spec:
      containers:
      - name: wordpress
        image: your-dockerhub-username/wordpress-custom:latest # Replace with your custom image
        ports:
        - containerPort: 80
        env:
        - name: WORDPRESS_DB_HOST
          value: mysql:3306
        - name: WORDPRESS_DB_USER
          value: wordpressuser
        - name: WORDPRESS_DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secrets
              key: password
        - name: WORDPRESS_DB_NAME
          value: wordpress
        - name: WORDPRESS_TABLE_PREFIX
          value: wp_
        # For headless, you might want to disable the theme/plugin editor
        # and potentially configure permalinks to be flat or disabled.
        # This can be done via wp-config.php or WP-CLI commands in your Dockerfile.
        # Example:
        # - name: WORDPRESS_CONFIG_EXTRA
        #   value: |
        #     define( 'DISALLOW_FILE_EDIT', true );
        #     define( 'WP_DEBUG', false );
        #     define( 'WP_POST_REVISIONS', false );
        #     update_option( 'permalink_structure', '' ); # Or a custom structure
        readinessProbe:
          httpGet:
            path: /wp-admin/admin-ajax.php?action=heartbeat # A simple check
            port: 80
          initialDelaySeconds: 15
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /wp-admin/admin-ajax.php?action=heartbeat
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 20
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "512Mi"
      # Consider using an initContainer to run WP-CLI commands for setup
      # e.g., setting permalinks, disabling file edits, etc.
      # initContainers:
      # - name: wordpress-init
      #   image: wordpress:cli
      #   command: ['wp', 'cli', 'command', 'here']
      #   env: ... (database credentials)
---
apiVersion: v1
kind: Service
metadata:
  name: wordpress
spec:
  selector:
    app: wordpress
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP # Or LoadBalancer if exposing directly

Kubernetes Secrets

apiVersion: v1
kind: Secret
metadata:
  name: mysql-secrets
type: Opaque
data:
  root-password: [BASE64_ENCODED_ROOT_PASSWORD] # e.g., bXlfcm9vdF9wYXNzd29yZA==
  password: [BASE64_ENCODED_DB_PASSWORD]       # e.g., bXlfcGFzc3dvcmQ=

To create the secrets, encode your passwords using base64:

echo -n 'your_root_password' | base64
echo -n 'your_db_password' | base64

Then, replace [BASE64_ENCODED_ROOT_PASSWORD] and [BASE64_ENCODED_DB_PASSWORD] in the secret manifest with the generated values.

Frontend Deployment

Your headless frontend application (e.g., React, Vue, Next.js) will be deployed as a separate Deployment and Service. This allows independent scaling and updates of the frontend and backend.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  labels:
    app: frontend
spec:
  replicas: 3 # Scale as needed
  selector:
    matchLabels:
      app: frontend
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: your-dockerhub-username/frontend-app:latest # Replace with your frontend image
        ports:
        - containerPort: 80 # Assuming your frontend serves on port 80
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "512Mi"
---
apiVersion: v1
kind: Service
metadata:
  name: frontend
spec:
  selector:
    app: frontend
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  type: ClusterIP

Ingress for External Access

To expose your frontend and potentially the WordPress admin area to the outside world, you’ll use an Ingress controller (like Nginx Ingress, Traefik, or HAProxy Ingress). This allows for sophisticated routing, SSL termination, and load balancing.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: main-ingress
  annotations:
    # Annotations specific to your Ingress controller (e.g., Nginx)
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  ingressClassName: nginx # Or your specific ingress class
  rules:
  - host: your-frontend-domain.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend
            port:
              number: 80
  - host: your-wordpress-admin.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: wordpress
            port:
              number: 80
  # Optional: If you want to proxy API requests from frontend to WordPress
  # - host: your-frontend-domain.com
  #   http:
  #     paths:
  #     - path: /wp-json/
  #       pathType: Prefix
  #       backend:
  #         service:
  #           name: wordpress
  #           port:
  #             number: 80
  # tls:
  # - hosts:
  #   - your-frontend-domain.com
  #   - your-wordpress-admin.com
  #   secretName: your-tls-secret # Kubernetes secret containing your TLS certificate

Ensure your DNS records point your-frontend-domain.com and your-wordpress-admin.com to the IP address of your Ingress controller.

Achieving Zero-Downtime Deployments

Kubernetes’ RollingUpdate strategy for Deployments is the cornerstone of zero-downtime updates. When you update the image tag in your WordPress Deployment manifest and apply it, Kubernetes orchestrates the update as follows:

  • Kubernetes creates a new Pod with the updated image.
  • It waits for the new Pod to become ready (passing its readiness probe).
  • Once the new Pod is ready, Kubernetes terminates an old Pod.
  • This process repeats until all old Pods are replaced by new ones.

The maxUnavailable and maxSurge parameters control the pace of this rollout. Setting maxUnavailable: 1 ensures that at least one Pod is always available, and maxSurge: 1 allows for one additional Pod to be created, facilitating a smooth transition.

Database Schema Migrations

Database schema changes are the trickiest part of zero-downtime deployments for stateful applications like WordPress. The standard RollingUpdate strategy can fail if the new application version requires a database schema that is incompatible with the old version. To mitigate this:

  • Backward Compatibility: Ensure new application versions are backward compatible with the existing database schema. This means any schema changes are additive or can be handled gracefully by the older version.
  • Phased Rollouts: Deploy the new application version to a subset of Pods first (e.g., by adjusting maxSurge and maxUnavailable or using advanced deployment tools like Argo Rollouts or Spinnaker).
  • Database Migration Tools: Integrate database migration tools (e.g., WP-CLI with custom scripts, or dedicated migration frameworks) into your CI/CD pipeline. These migrations should ideally be run as an initContainer in your WordPress Deployment, ensuring they complete before the main application container starts.
  • Readiness Probe Logic: The readiness probe should ideally check not just if the application is running, but also if it’s ready to serve traffic *with the current database schema*.

For headless WordPress, the impact of schema changes on the API is critical. If your API endpoints change significantly, your frontend will break. Always test API compatibility thoroughly.

Caching Strategies

Effective caching is paramount for performance and scalability. For headless WordPress, consider:

  • Object Caching: Use Redis or Memcached for WordPress object caching. This can be deployed as a separate Kubernetes Deployment and Service, and configured in wp-config.php.
  • Page Caching: While WordPress itself can cache pages, for high-traffic headless sites, it’s often better to implement caching at the edge (e.g., Cloudflare, Akamai) or within your Ingress controller.
  • CDN: Serve static assets (images, CSS, JS) from a Content Delivery Network.
  • Frontend Caching: Implement caching strategies within your frontend application (e.g., using service workers, HTTP caching headers).

Monitoring and Logging

Robust monitoring and logging are essential for maintaining a production-ready Kubernetes deployment.

  • Metrics: Deploy Prometheus and Grafana to collect and visualize metrics from your WordPress pods, database, and Ingress controller. Monitor CPU/memory usage, request latency, error rates, and database connection counts.
  • Logging: Implement a centralized logging solution (e.g., EFK stack – Elasticsearch, Fluentd, Kibana, or Loki/Promtail/Grafana). Configure your containers to log to stdout and stderr, and use a log collector to aggregate logs for analysis.
  • Health Checks: Ensure your readinessProbe and livenessProbe are well-configured and accurately reflect the health of your WordPress application.

Conclusion

By combining Docker Compose for local development with a well-architected Kubernetes deployment, you can achieve a highly scalable, resilient, and maintainable headless WordPress setup. The key lies in decoupling the frontend and backend, leveraging Kubernetes’ native features for orchestration and zero-downtime updates, and implementing robust strategies for database management, caching, and monitoring. This approach empowers content creators with a powerful CMS while providing frontend developers with the freedom to build modern, performant user experiences.

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

  • Leveraging Docker Compose and Kubernetes for Scalable, Zero-Downtime WordPress Headless Deployments
  • Orchestrating High-Availability WordPress with Headless CMS and Multi-Region AWS Deployments
  • Leveraging PHP 9’s JIT and GC Enhancements for Microservice Performance at Scale with Laravel
  • Leveraging PHP 8.3’s JIT and Vector API for High-Performance Microservices with Laravel Octane and Docker Swarm
  • Leveraging Docker’s Multi-Stage Builds and Cache Optimization for Ultra-Fast PHP 9 Laravel Deployments on AWS

Categories

  • apache (1)
  • AWS (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (664)
  • Desktop Applications (14)
  • DevOps (87)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (88)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • Performance & Security Optimization (10)
  • PHP (310)
  • PHP Development (49)
  • Plugins & Themes (244)
  • Programming Languages (10)
  • Python (20)
  • Ruby on Rails (1)
  • Security (1)
  • Security & Compliance (650)
  • SEO & Growth (492)
  • Server (118)
  • Softwares (1)
  • Ubuntu (9)
  • Uncategorized (628)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (169)
  • WordPress Plugin Development (728)
  • WordPress Theme Development (357)

Recent Posts

  • Leveraging Docker Compose and Kubernetes for Scalable, Zero-Downtime WordPress Headless Deployments
  • Orchestrating High-Availability WordPress with Headless CMS and Multi-Region AWS Deployments
  • Leveraging PHP 9's JIT and GC Enhancements for Microservice Performance at Scale with Laravel

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (873)
  • WordPress Plugin Development (728)
  • Debugging & Troubleshooting (664)
  • Security & Compliance (650)
  • Uncategorized (628)

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