Self-Hosting WordPress on a 3-Node Raspberry Pi Kubernetes Cluster: The Complete Engineering Guide

A systematic look at hardware conditioning, lightweight K3s orchestration, edge routing with Traefik, and automated TLS certificate provisioning via Let’s Encrypt.

1. Introduction & Core Concept

Building an enterprise-grade web architecture at home no longer requires expensive cloud accounts or noisy server racks. By utilizing the modern Raspberry Pi ecosystem alongside highly efficient container orchestrators, engineers and home lab enthusiasts can deploy high-availability clusters right on their desks. This guide walks step-by-step through setting up a production-ready, three-node Kubernetes cluster optimized to run a secure, fast, and resilient instance of WordPress.

While standard Kubernetes (K8s) can overwhelm single-board computers due to heavy resource overhead, Rancher’s K3s distribution completely solves this. K3s strips away legacy, non-essential cloud providers and alpha features, packaging a fully compliant Kubernetes environment into a single lightweight binary that consumes under 512MB of RAM per node—making it perfect for the ARM architecture of the Raspberry Pi.

2. Phase 1: Hardware Hardening & Operating System Preparation

For optimal performance and cluster stability, this architecture mandates a three-node setup consisting of one Control Plane (Master Server) and two Worker Nodes (Agents).

  • Hardware Choice: Raspberry Pi 4 or Raspberry Pi 5 computers with a minimum of 4GB of RAM (8GB is highly recommended for higher traffic thresholds).
  • Storage Media: High-end Class 10/UHS-1 MicroSD cards, or ideally, USB 3.0 Solid State Drives (SSDs) to prevent disk I/O bottlenecks during database-heavy operations.
  • Networking: A dedicated gigabit ethernet switch connecting all three units to eliminate latency and dropped packets inherent to Wi-Fi.

Step 1.1: Flash the OS and Bind Static IPs

Using the official Raspberry Pi Imager, flash the 64-bit edition of Raspberry Pi OS Lite onto your media blocks. Avoid using 32-bit versions, as Kubernetes relies heavily on 64-bit memory mappings. Assign clear hostnames to each node during the flashing or first boot process to isolate your workflow:

• node-01 (Primary Cluster Server)
• node-02 (Worker Agent 01)
• node-03 (Worker Agent 02)

Once online, log into your local router configuration page and bind static local IP addresses to each device’s MAC address. For the purpose of this blueprint, we will assume the following layout:

• node-01: 192.168.1.50
• node-02: 192.168.1.51
• node-03: 192.168.1.52

Step 1.2: Enable Kernel Linux Control Groups (Cgroups)

By default, Raspberry Pi OS does not ship with memory resource limiting active. Kubernetes requires this mechanism to throttle container limits dynamically. Execute the following command on all three nodes:

sudo nano /boot/firmware/cmdline.txt

Append the following parameters to the extreme end of the existing single line of configuration. Do not break the file into multiple lines, as the bootloader will ignore parameters on subsequent lines:

cgroup_memory=1 cgroup_enable=memory

Step 1.3: Completely Disable Swap Memory

Kubernetes scheduling models rely on precise memory accounting. If a node begins swapping memory onto an SD card or disk, the cluster scheduler cannot accurately determine resource allocation, causing cascading scheduling timeouts. Run these commands on all three nodes to purge the swap software entirely:

sudo dphys-swapfile swapoff && sudo dphys-swapfile uninstallsudo apt purge dphys-swapfile -y

Step 1.4: Mitigate Split-Brain Multi-IP and Network Failures

If your node reveals multiple IP addresses under ‘hostname -I’, it means both Ethernet (eth0) and Wi-Fi (wlan0) interfaces are simultaneously active. This causes severe cluster mapping instability if K3s binds to a Wi-Fi interface that periodically experiences signal degradation. Disable the Wi-Fi radio completely by opening the main firmware configuration:

sudo nano /boot/firmware/config.txt

Add the following directive to the bottom of the file:

dtoverlay=disable-wifi

Additionally, if your cluster nodes present internet connectivity but drop errors stating ‘Temporary failure resolving archive.raspberrypi.com’, your system is suffering a DNS breakdown. Bypass standard router DHCP assignment issues by permanently appending secure upstream nameservers. Modify your configuration via NetworkManager:

sudo nmcli connection modify “preconfigured” ipv4.dns “8.8.8.8 1.1.1.1″sudo nmcli connection up “preconfigured”

Reboot each node to fully apply all low-level system adjustments:

sudo reboot

3. Phase 2: Building the K3s Orchestration Layer

With the underlying operating systems hardened, we proceed to initiate our Kubernetes cluster fabric. The control plane will maintain the tracking state, while the agent units run the application layers.

Step 2.1: Initialize the Server Control Plane (Node 1)

Establish an SSH connection to your primary node (node-01 at 192.168.1.50) and execute the standard K3s installer script. Note that we explicitly define the binding node IP to guarantee proper communication across the physical Ethernet interface:

curl -sfL https://get.k3s.io | sh -s – –node-ip=192.168.1.50

Once completed, extract the securely generated Node Token from the control plane filesystem. This cryptographically signs node joining requests:

sudo cat /var/lib/rancher/k3s/server/node-token

Copy this long token string and save it to a secure temporary clipboard.

Step 2.2: Enroll Worker Nodes (Node 2 & Node 3)

SSH into node-02 and node-03 respectively, and execute the installation engine while passing the master control URL parameters along with the copied cluster token:

curl -sfL https://get.k3s.io | K3S_URL=https://192.168.1.50:6443 \  K3S_TOKEN=K10a762b…YOUR_TOKEN… \  sh -s – –node-ip=<CURRENT_NODE_LOCAL_IP>

Step 2.3: Verify Cluster Control Health

Return to your primary Server node (node-01) and run the cluster topology inspector to confirm that all nodes are recognized and ready to receive application workloads:

sudo kubectl get nodes

Your terminal should return a matrix mirroring this clean state:

NAME      STATUS   ROLES                  AGE     VERSIONnode-01   Ready    control-plane,master   3m45s   v1.28.x+k3s1node-02   Ready    <none>                 92s     v1.28.x+k3s1node-03   Ready    <none>                 84s     v1.28.x+k3s1

4. Phase 3: Architecting Core Workloads & Database Fabric

To keep our cluster neat, we will encapsulate all elements inside an isolated, declarative logical boundary known as a Namespace. Furthermore, we will inject a native Kubernetes Secret object to shield the database administration credentials from plain-text files.

kubectl create namespace wordpresskubectl create secret generic mysql-pass –namespace=wordpress –from-literal=password=VaultSecurePass99

Step 3.1: Construct the Relational Database Engine (MariaDB)

Standard MySQL structures lack comprehensive optimizations for low-power ARM environments. Therefore, we utilize the official MariaDB image which functions seamlessly on single-board devices. We declare a PersistentVolumeClaim (PVC) requesting 2 Gigabytes of space. K3s automatically binds this request to its built-in local-path provisioner, writing cluster data transparently to your Pi’s system storage without requiring third-party cloud infrastructure.

Create a localized resource file named mariadb.yaml:

apiVersion: v1kind: PersistentVolumeClaimmetadata:  name: mariadb-pvc  namespace: wordpressspec:  accessModes: [ReadWriteOnce]  resources: { requests: { storage: 2Gi } }—apiVersion: apps/v1kind: Deploymentmetadata:  name: wordpress-db  namespace: wordpressspec:  selector:    matchLabels: { app: wordpress, tier: mysql }  template:    metadata:      labels: { app: wordpress, tier: mysql }    spec:      containers:      – image: mariadb:10.6        name: mysql        env:        – name: MYSQL_ROOT_PASSWORD          valueFrom: { secretKeyRef: { name: mysql-pass, key: password } }        ports:        – containerPort: 3306          name: mysql        volumeMounts:        – name: mysql-data          mountPath: /var/lib/mysql      volumes:      – name: mysql-data        persistentVolumeClaim: { claimName: mariadb-pvc }—apiVersion: v1kind: Servicemetadata:  name: wordpress-db  namespace: wordpressspec:  ports: [{ port: 3306 }]  selector: { app: wordpress, tier: mysql }

Instantly execute this infrastructure blueprint inside your cluster via kubectl:

kubectl apply -f mariadb.yaml

Step 3.2: Construct the Frontend Application Engine

Next, we define the web application tier. The WordPress deployment container receives environment variables referencing the internal DNS address of our database service (‘wordpress-db’) alongside the injected password secret object. Create a localized configuration file named wordpress.yaml:

apiVersion: v1kind: PersistentVolumeClaimmetadata:  name: wp-pvc  namespace: wordpressspec:  accessModes: [ReadWriteOnce]  resources: { requests: { storage: 2Gi } }—apiVersion: apps/v1kind: Deploymentmetadata:  name: wordpress  namespace: wordpressspec:  selector:    matchLabels: { app: wordpress, tier: frontend }  template:    metadata:      labels: { app: wordpress, tier: frontend }    spec:      containers:      – image: wordpress:latest        name: wordpress        env:        – name: WORDPRESS_DB_HOST          value: wordpress-db        – name: WORDPRESS_DB_PASSWORD          valueFrom: { secretKeyRef: { name: mysql-pass, key: password } }        ports:        – containerPort: 80          name: wordpress        volumeMounts:        – name: wp-data          mountPath: /var/www/html      volumes:      – name: wp-data        persistentVolumeClaim: { claimName: wp-pvc }—apiVersion: v1kind: Servicemetadata:  name: wordpress  namespace: wordpressspec:  ports: [{ port: 80 }]  selector: { app: wordpress, tier: frontend }

Apply the application blueprint using the cluster control engine:

kubectl apply -f wordpress.yaml

5. Phase 4: Edge Routing, Ingress Controllers, and Automated SSL Certs

To establish routing for a custom domain (e.g., yourdomain.com) without clunky port assignments, we use a dedicated edge gatekeeper. K3s ships with Traefik preconfigured as its standard Ingress Controller. We will direct external traffic smoothly through Traefik using an Ingress rule, and leverage Cert-Manager to communicate with Let’s Encrypt for fully automated TLS (HTTPS) certificates.

Step 4.1: Deploy the Cert-Manager Controller

Inject the official automated certificate management project into your cluster. This engine watches for special ingress configuration rules and requests public web keys automatically:

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml

Step 4.2: Provision the Cryptographic Issuer

Create a manifest defining a ClusterIssuer. This component acts as the secure identity bridge pointing directly to the production API servers of Let’s Encrypt. Save this file locally as issuer.yaml:

apiVersion: cert-manager.io/v1kind: ClusterIssuermetadata:  name: letsencrypt-prodspec:  acme:    server: https://acme-v02.api.letsencrypt.org/directory    email: [email protected]  # <— Input your actual working email    privateKeySecretRef:      name: letsencrypt-prod    solvers:    – http01:        ingress:          class: traefik

Execute the configuration file:

kubectl apply -f issuer.yaml

Step 4.3: Deploy the Production TLS Ingress Manifest

Now, construct the core gateway blueprint file named wp-ingress.yaml. This maps your external custom domain straight to the backend WordPress service layer while forcing cert-manager to fetch and renew SSL layers automatically:

apiVersion: networking.k8s.io/v1kind: Ingressmetadata:  name: wordpress-ingress  namespace: wordpress  annotations:    cert-manager.io/cluster-issuer: “letsencrypt-prod”    kubernetes.io/ingress.class: traefikspec:  tls:  – hosts:    – yourdomain.com                # <— Insert your real domain name    secretName: wp-site-tls-cert  rules:  – host: yourdomain.com            # <— Insert your real domain name    http:      paths:      – path: /        pathType: Prefix        backend:          service:            name: wordpress            port:              number: 80

Apply the final gateway ingress manifest:

kubectl apply -f wp-ingress.yaml

6. Phase 5: Border Security & Public DNS Provisioning

With your internal infrastructure fully configured, you must bridge the network gap between the wider internet and your local home laboratory cluster.

Step 5.1: Configure Router Boundary Forwarding

Log into your local residential router management console and navigate to Port Forwarding / Virtual Server options. Map external ports across the firewall directly to your Control Plane Server IP (192.168.1.50):

• Route WAN Port 80 -> Forward to Local IP 192.168.1.50 on Port 80
• Route WAN Port 443 -> Forward to Local IP 192.168.1.50 on Port 443

Step 5.2: Bind the Authoritative DNS A Record

Navigate to your public domain registrar dashboard (e.g., Namecheap, Cloudflare, GoDaddy). Locate your domain DNS control board and create an absolute ‘A Record’:

• Host: @ (or subdomains like ‘www’)
• Value: Your Home Network’s External Public WAN IP Address

Step 5.3: Track Certificate Execution and Launch

Cert-manager will detect the new ingress mapping, initiate a cryptographic HTTP-01 challenge against the Let’s Encrypt authority server, and finalize a valid certificate. Track this initialization process via the command line:

kubectl get certificate -n wordpress

Once the status columns shift from ‘False’ or blank to ‘READY: True’, your system is entirely secure. Open any modern browser interface and navigate to https://yourdomain.com. You will immediately be greeted by the secure WordPress configuration setup wizard, fully hosted right from your private 3-node Raspberry Pi home laboratory!

7. Reference Architecture Summary

The technical summary below details how the cluster handles data and entry flows across components:

Component GroupSelected Engineering ComponentFunctional Objective
Orchestrator CoreK3s by Rancher LabsProvides standard K8s API operations stripped of heavy enterprise components, operating comfortably under 512MB RAM.
Database ManagementMariaDB 10.6 InstanceHandles relational SQL state structures cleanly on ARM architectures while relying on local storage loops.
Edge Ingress InflowTraefik Engine (Built-in)Exposes cluster entry boundaries on standard web ports, interpreting host request rules.
Security HandlingCert-Manager + Let’s EncryptManages life cycle tracks, automatic issuing, and seamless 90-day updates for TLS network blocks.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top