• 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 » Orchestrating Microservices with Docker Swarm: A Scalable and Resilient WordPress Headless Architecture

Orchestrating Microservices with Docker Swarm: A Scalable and Resilient WordPress Headless Architecture

Docker Swarm for Headless WordPress: Core Concepts

Deploying a headless WordPress architecture demands robust orchestration for scalability and resilience. Docker Swarm offers a native, integrated solution for container orchestration that is often overlooked in favor of more complex platforms like Kubernetes. For many use cases, Swarm provides a simpler, yet powerful, alternative. This post details how to set up and manage a headless WordPress stack using Docker Swarm, focusing on the database, WordPress core, and a reverse proxy/API gateway.

Our architecture will consist of three primary services:

  • Database: A highly available MySQL cluster (or MariaDB).
  • WordPress Core: The PHP-FPM and Nginx/Apache components serving the WordPress REST API and admin interface.
  • Reverse Proxy/API Gateway: Nginx or Traefik to handle SSL termination, routing, and potentially rate limiting or authentication for API requests.

We’ll leverage Docker Swarm’s built-in features like service discovery, load balancing, rolling updates, and secrets management.

Setting Up the Swarm Cluster

Before deploying services, you need a Docker Swarm cluster. This can be a single manager node for testing or multiple manager and worker nodes for production. Initialize the swarm on your manager node:

On the manager node:

docker swarm init --advertise-addr 

This command will output a `docker swarm join` command. Run this on your worker nodes to add them to the swarm.

To verify your nodes:

docker node ls

Database Layer: Highly Available MySQL/MariaDB

For production, a single database instance is a single point of failure. We’ll use a multi-node MySQL/MariaDB setup orchestrated by Docker Swarm. A common pattern is to use a replication setup (primary-replica) or a Galera Cluster for synchronous multi-master replication. For simplicity and common use cases, we’ll outline a primary-replica setup with a proxy for read/write splitting.

We’ll define a Docker Compose file (which Swarm understands) to manage these services.

MariaDB Primary-Replica with ProxySQL

ProxySQL is an intelligent proxy for MySQL that can handle load balancing, query routing, and failover. We’ll deploy it alongside our MariaDB instances.

Create a file named docker-compose.yml:

[version: '3.7']

services:
  db-primary:
    image: mariadb:10.6
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress_user
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - db_primary_data:/var/lib/mysql
    networks:
      - app-network
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.role == manager # Or a dedicated DB node role

  db-replica:
    image: mariadb:10.6
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    volumes:
      - db_replica_data:/var/lib/mysql
    networks:
      - app-network
    depends_on:
      - db-primary
    deploy:
      replicas: 2 # Deploy two replicas for HA
      placement:
        constraints:
          - node.role == worker # Or a dedicated DB node role

  db-proxy:
    image: proxysql/proxysql:2.3.1
    restart: always
    ports:
      - "6033:6033" # MySQL client port
      - "7070:7070" # ProxySQL admin interface
      - "9090:9090" # ProxySQL stats interface
    environment:
      MYSQL_HOST: db-primary # Initial host for ProxySQL config
      MYSQL_PORT: 3306
      MYSQL_USER: wordpress_user
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    volumes:
      - proxysql_data:/etc/proxysql
    networks:
      - app-network
    depends_on:
      - db-primary
      - db-replica
    deploy:
      replicas: 2 # HA for the proxy
      placement:
        constraints:
          - node.role == manager # Or a dedicated proxy node role

volumes:
  db_primary_data:
  db_replica_data:
  proxysql_data:

networks:
  app-network:
    driver: overlay

Explanation:

  • db-primary: A single MariaDB instance. In a real production setup, you’d configure replication from this primary to the replicas. For simplicity here, we’re assuming manual or external configuration of replication, or you might opt for a Galera cluster image.
  • db-replica: Two instances of MariaDB. These would be configured as replicas of db-primary. Docker Swarm will ensure these services are running.
  • db-proxy: ProxySQL. It’s configured to connect to the database nodes. We’ll need to further configure ProxySQL to manage the primary/replica setup and routing.
  • volumes: Using named volumes for persistent data.
  • networks: An overlay network for inter-container communication across nodes.
  • deploy.replicas: Specifies the desired number of instances for each service. Swarm will maintain this count.
  • deploy.placement.constraints: Ensures services are scheduled on specific node types (e.g., managers for critical services, workers for others).

Note on Replication: The above configuration doesn’t automatically set up MariaDB replication. You would typically achieve this by:

  • Using a MariaDB image that supports Galera Cluster (e.g., mariadb:10.6-galera) and configuring it appropriately.
  • Manually configuring replication after the containers are up, or using an entrypoint script that sets up replication.
  • Using a dedicated database orchestration tool.

For ProxySQL configuration, you’d typically mount a custom proxysql.cnf file or use environment variables to bootstrap its configuration. This involves defining hostgroups, users, and query rules for read/write splitting.

To deploy this stack:

# Set your environment variables (e.g., in a .env file or directly)
export MYSQL_ROOT_PASSWORD='your_strong_root_password'
export MYSQL_PASSWORD='your_strong_db_password'

# Deploy the stack
docker stack deploy -c docker-compose.yml wordpress_db

Check the status:

docker stack services wordpress_db
docker service ps wordpress_db_db-primary

WordPress Core Service

The WordPress core service will consist of PHP-FPM and a web server (Nginx in this example) to serve the REST API and the admin dashboard. We’ll also need to mount the WordPress files.

WordPress Files Management

For headless WordPress, the actual WordPress core files (PHP, themes, plugins) need to be accessible by the PHP-FPM container. Several strategies exist:

  • Shared Volume: Mount a named volume or host directory containing WordPress files. This is simple but can lead to synchronization issues if not managed carefully.
  • Build-time Integration: Bake WordPress core, themes, and plugins into a custom Docker image. This is the most robust approach for production.
  • External File System: Use a distributed file system (e.g., NFS, GlusterFS) mounted on all nodes.

We’ll assume a custom Docker image for WordPress core for this example, as it’s best practice. This image would be built from a Dockerfile that includes WordPress, necessary PHP extensions, and your custom themes/plugins.

WordPress Dockerfile Example

Create a file named Dockerfile.wordpress:

# Use an official PHP-FPM image with Apache or Nginx
FROM php:8.1-fpm

# Install necessary extensions for WordPress
RUN apt-get update && apt-get install -y \
    libzip-dev \
    libpng-dev \
    libjpeg-dev \
    libfreetype6-dev \
    libssl-dev \
    libwebp-dev \
    git \
    unzip \
    && rm -rf /var/lib/apt/lists/* \
    && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \
    && docker-php-ext-install -j$(nproc) gd zip exif pcntl sockets \
    && docker-php-ext-enable gd zip exif pcntl sockets

# Install Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# Download WordPress core
RUN curl -o wordpress.tar.gz -SL https://wordpress.org/latest.tar.gz \
    && tar -xzf wordpress.tar.gz -C /var/www/html --strip-components=1 \
    && rm wordpress.tar.gz

# Install plugins and themes (example)
# COPY --chown=www-data:www-data ./wp-content/plugins/ /var/www/html/wp-content/plugins/
# COPY --chown=www-data:www-data ./wp-content/themes/ /var/www/html/wp-content/themes/

# Set ownership for WordPress files
RUN chown -R www-data:www-data /var/www/html

# Expose the PHP-FPM port
EXPOSE 9000

# Set the working directory
WORKDIR /var/www/html

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

Build this image:

docker build -t your-dockerhub-username/wordpress-core:latest -f Dockerfile.wordpress .
docker push your-dockerhub-username/wordpress-core:latest

WordPress Service Definition

Now, let’s integrate this into our docker-compose.yml. We’ll add the WordPress core service and a separate Nginx service that acts as a reverse proxy for the PHP-FPM container.

Update your docker-compose.yml:

[version: '3.7']

services:
  # ... (db services from previous section) ...

  wordpress:
    image: your-dockerhub-username/wordpress-core:latest # Your custom image
    restart: always
    environment:
      WORDPRESS_DB_HOST: wordpress_db_db-proxy:3306 # Use the ProxySQL service
      WORDPRESS_DB_USER: wordpress_user
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wordpress_data:/var/www/html # Persistent storage for uploads, etc.
    networks:
      - app-network
    depends_on:
      - wordpress_db_db-proxy # Ensure DB proxy is available
    deploy:
      replicas: 3 # Scale WordPress instances
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure

  wordpress-web:
    image: nginx:latest
    restart: always
    ports:
      - "80:80" # Public facing port
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro # Mount custom Nginx config
      - wordpress_data:/var/www/html # Mount WordPress data for static assets
    networks:
      - app-network
    depends_on:
      - wordpress
    deploy:
      replicas: 3 # Scale Nginx instances
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure

volumes:
  db_primary_data:
  db_replica_data:
  proxysql_data:
  wordpress_data: # Persistent storage for WordPress files

networks:
  app-network:
    driver: overlay

Create a basic nginx.conf file for the wordpress-web service:

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 768;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    ssl_protocols TLSv1.2 TLSv1.3; # Dropping older TLS versions
    ssl_prefer_server_ciphers on;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    gzip on;

    # Upstream for PHP-FPM
    upstream php-fpm {
        # Use Docker's service discovery to find the 'wordpress' service
        # Swarm will load balance across replicas
        server wordpress:9000;
    }

    server {
        listen 80;
        server_name localhost; # Replace with your domain

        root /var/www/html;
        index index.php index.html index.htm;

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

        # Pass PHP scripts to FastCGI server
        location ~ \.php$ {
            include snippets/fastcgi-php.conf;
            # Use Docker's service discovery to connect to the 'wordpress' service
            fastcgi_pass wordpress:9000;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }

        # Deny access to sensitive files
        location ~ /\.ht {
            deny all;
        }

        # Serve static files directly
        location ~* \.(jpg|jpeg|gif|png|css|js|ico|webp|svg)$ {
            expires 30d;
            add_header Cache-Control "public";
        }
    }
}

Deploy this updated stack:

# Ensure environment variables are set
export MYSQL_ROOT_PASSWORD='your_strong_root_password'
export MYSQL_PASSWORD='your_strong_db_password'

# Deploy the full stack
docker stack deploy -c docker-compose.yml wordpress_stack

You can now access your WordPress admin panel and REST API via the IP address of any node running the wordpress-web service, on port 80.

Reverse Proxy and API Gateway (Traefik)

For production, you’ll want a dedicated reverse proxy like Traefik to handle SSL termination, dynamic routing, and potentially more advanced API gateway features. Traefik integrates seamlessly with Docker Swarm.

Traefik Configuration

Create a docker-compose.traefik.yml file:

[version: '3.7']

services:
  traefik:
    image: traefik:v2.9 # Use a specific version
    command:
      # Enable Docker provider
      - "--providers.docker=true"
      # Enable Docker Swarm provider
      - "--providers.docker.swarmmode=true"
      # Expose Traefik dashboard
      - "--api.dashboard=true"
      # Enable access logs
      - "--accesslog=true"
      # Set entrypoints (ports)
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      # Enable Let's Encrypt (replace with your domain and email)
      - "--certificatesresolvers.myresolver.acme.tlschallenge=true"
      - "--certificatesresolvers.myresolver.acme.email=your-email@example.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"   # HTTP
      - "443:443" # HTTPS
      - "8080:8080" # Traefik Dashboard
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt # For Let's Encrypt certificates
    networks:
      - traefik-public # A public network for external access
    deploy:
      replicas: 2
      placement:
        constraints:
          - node.role == manager # Run Traefik on manager nodes

networks:
  traefik-public:
    external: true

You’ll need to create the traefik-public network if it doesn’t exist:

docker network create --driver overlay traefik-public

And create an empty letsencrypt/acme.json file and set its permissions:

mkdir letsencrypt
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.json

Configuring WordPress and Nginx for Traefik

We need to adjust our docker-compose.yml to use Traefik for routing and SSL. Remove the public port mapping from wordpress-web and add Traefik labels.

Update docker-compose.yml:

[version: '3.7']

services:
  # ... (db services) ...

  wordpress:
    image: your-dockerhub-username/wordpress-core:latest
    restart: always
    environment:
      WORDPRESS_DB_HOST: wordpress_db_db-proxy:3306
      WORDPRESS_DB_USER: wordpress_user
      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wordpress_data:/var/www/html
    networks:
      - app-network
      - traefik-public # Connect to the public network
    depends_on:
      - wordpress_db_db-proxy
    deploy:
      replicas: 3
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure
    labels:
      # Traefik labels for routing to WordPress
      - "traefik.enable=true"
      - "traefik.http.routers.wordpress.rule=Host(`your-wordpress-domain.com`)" # Your domain
      - "traefik.http.routers.wordpress.entrypoints=websecure" # Use HTTPS entrypoint
      - "traefik.http.routers.wordpress.tls.certresolver=myresolver" # Use Let's Encrypt resolver
      - "traefik.http.services.wordpress.loadbalancer.server.port=80" # Nginx listens on 80 internally

  wordpress-web:
    image: nginx:latest
    restart: always
    # REMOVE: ports: - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - wordpress_data:/var/www/html
    networks:
      - app-network
      - traefik-public # Connect to the public network
    depends_on:
      - wordpress
    deploy:
      replicas: 3
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure
    labels:
      # Traefik labels for routing to Nginx (which serves static assets and proxies PHP)
      # This is technically redundant if wordpress router points to wordpress service,
      # but good for clarity or if you had separate Nginx for static assets.
      # For this setup, the wordpress router should point to the wordpress service,
      # and the wordpress service's internal port is 80 (where nginx is listening).
      # If you want Traefik to directly route to Nginx, you'd label wordpress-web instead.
      # Let's assume wordpress router points to wordpress service, and wordpress service
      # internally routes to nginx.
      # If you want Traefik to route directly to Nginx:
      - "traefik.enable=true"
      - "traefik.http.routers.wordpress-web.rule=Host(`your-wordpress-domain.com`)"
      - "traefik.http.routers.wordpress-web.entrypoints=websecure"
      - "traefik.http.routers.wordpress-web.tls.certresolver=myresolver"
      - "traefik.http.services.wordpress-web.loadbalancer.server.port=80" # Nginx listens on 80

volumes:
  db_primary_data:
  db_replica_data:
  proxysql_data:
  wordpress_data:

networks:
  app-network:
    driver: overlay
  traefik-public:
    external: true

Deploy Traefik first:

docker stack deploy -c docker-compose.traefik.yml traefik

Then redeploy your main WordPress stack:

# Ensure environment variables are set
export MYSQL_ROOT_PASSWORD='your_strong_root_password'
export MYSQL_PASSWORD='your_strong_db_password'

# Deploy the full stack
docker stack deploy -c docker-compose.yml wordpress_stack

Now, access your WordPress site via https://your-wordpress-domain.com. Traefik will handle SSL and route traffic to your WordPress instances. You can access the Traefik dashboard at http://:8080.

Monitoring and Management

Docker Swarm provides basic monitoring and management capabilities:

  • Service Status: docker stack services [stack_name] and docker service ps [service_name].
  • Logs: docker service logs [service_name].
  • Resource Usage: Use Docker’s built-in stats or integrate with external monitoring tools like Prometheus/Grafana via exporters.
  • Health Checks: Define health checks in your docker-compose.yml (e.g., for Nginx, check /; for WordPress, check /wp-json/wp/v2/posts). Swarm will automatically restart unhealthy containers.

Advanced Considerations

Security:

  • Use Docker Secrets for sensitive information (passwords, API keys) instead of environment variables in production.
  • Configure firewall rules to restrict access to database ports.
  • Regularly update Docker and your service images.

Scalability:

  • Scale services up or down using docker service scale [service_name]=N.
  • Monitor resource utilization and adjust replica counts accordingly.
  • Consider dedicated nodes for database, application, and proxy services.

CI/CD:

  • Automate Docker image builds and pushes to a registry.
  • Use CI/CD pipelines to deploy updated stacks using docker stack deploy.

Caching: Implement caching strategies (e.g., Redis, Varnish) for improved performance, especially for API responses.

Docker Swarm provides a powerful, yet often underestimated, platform for orchestrating complex applications like headless WordPress. By leveraging its native features and integrating with tools like Traefik, you can build a scalable, resilient, and secure architecture.

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

  • Orchestrating High-Availability WordPress with Docker Swarm and AWS Load Balancing: A Deep Dive into Infrastructure as Code
  • Leveraging PHP 8.3’s JIT and Vector API for High-Performance WordPress Headless Backends on AWS Fargate
  • Leveraging PHP 8.3 JIT and Laravel Octane for Sub-Millisecond API Responses: A Performance Deep Dive
  • Orchestrating Microservices with Docker Swarm: A Scalable and Resilient WordPress Headless Architecture
  • Beyond `php-fpm`: Orchestrating High-Concurrency PHP with Custom Event Loops and WebSockets on Kubernetes

Categories

  • apache (1)
  • AWS (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (664)
  • Desktop Applications (14)
  • DevOps (75)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (78)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (873)
  • Performance & Security Optimization (8)
  • PHP (268)
  • 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 (533)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (140)
  • WordPress Plugin Development (728)
  • WordPress Theme Development (357)

Recent Posts

  • Orchestrating High-Availability WordPress with Docker Swarm and AWS Load Balancing: A Deep Dive into Infrastructure as Code
  • Leveraging PHP 8.3's JIT and Vector API for High-Performance WordPress Headless Backends on AWS Fargate
  • Leveraging PHP 8.3 JIT and Laravel Octane for Sub-Millisecond API Responses: A Performance Deep Dive

Top Categories

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

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