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 Group | Selected Engineering Component | Functional Objective |
| Orchestrator Core | K3s by Rancher Labs | Provides standard K8s API operations stripped of heavy enterprise components, operating comfortably under 512MB RAM. |
| Database Management | MariaDB 10.6 Instance | Handles relational SQL state structures cleanly on ARM architectures while relying on local storage loops. |
| Edge Ingress Inflow | Traefik Engine (Built-in) | Exposes cluster entry boundaries on standard web ports, interpreting host request rules. |
| Security Handling | Cert-Manager + Let’s Encrypt | Manages life cycle tracks, automatic issuing, and seamless 90-day updates for TLS network blocks. |
