Goal: Deploy a self-hosted WordPress site on your k3s cluster using:
- Longhorn for persistent config storage
- MetalLB for a static IP
- Cloudflare Tunnel for external access
This is a standard procedure for publishing most apps going forward.
Step 1: Prerequisites
Make sure you:
- Have a working k3s cluster
- Installed Longhorn and set up a default StorageClass
- Have a running NFS server (e.g., TrueNAS at 10.0.0.3)
- Have MetalLB configured (we’ll use IP 10.0.0.35)
- Have kubectl configured from your jumpbox
📁 Step 2: Create Namespace
kubectl create namespace web
💾 Step 3: Create Persistent Volume Claims
Save the following as wordpress-pvc.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wordpress-config
namespace: web
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: longhorn
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wordpress-media
namespace: web
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 500Gi
nfs:
path: /mnt/tank/data/wordpress
server: 10.0.0.3
Apply it:
kubectl apply -f wordpress-pvc.yaml
🐬 Step 4: Deploy MariaDB
Save this as mariadb.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mariadb-data
namespace: web
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: longhorn
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mariadb
namespace: web
spec:
selector:
matchLabels:
app: mariadb
replicas: 1
template:
metadata:
labels:
app: mariadb
spec:
containers:
- name: mariadb
image: mariadb:10.6
env:
- name: MYSQL_ROOT_PASSWORD
value: my-secret-pw
- name: MYSQL_DATABASE
value: wordpress
- name: MYSQL_USER
value: wordpress
- name: MYSQL_PASSWORD
value: wordpresspass
volumeMounts:
- mountPath: /var/lib/mysql
name: mariadb-storage
volumes:
- name: mariadb-storage
persistentVolumeClaim:
claimName: mariadb-data
---
apiVersion: v1
kind: Service
metadata:
name: mariadb
namespace: web
spec:
ports:
- port: 3306
selector:
app: mariadb
Apply it:
kubectl apply -f mariadb.yaml
🌐 Step 5: Deploy WordPress
Save as wordpress.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
namespace: web
spec:
selector:
matchLabels:
app: wordpress
replicas: 1
template:
metadata:
labels:
app: wordpress
spec:
containers:
- name: wordpress
image: wordpress:latest
env:
- name: WORDPRESS_DB_HOST
value: mariadb.web.svc.cluster.local
- name: WORDPRESS_DB_NAME
value: wordpress
- name: WORDPRESS_DB_USER
value: wordpress
- name: WORDPRESS_DB_PASSWORD
value: wordpresspass
volumeMounts:
- mountPath: /var/www/html
name: wordpress-storage
- mountPath: /var/www/html/wp-content/uploads
name: media-storage
volumes:
- name: wordpress-storage
persistentVolumeClaim:
claimName: wordpress-config
- name: media-storage
persistentVolumeClaim:
claimName: wordpress-media
---
apiVersion: v1
kind: Service
metadata:
name: wordpress
namespace: web
spec:
selector:
app: wordpress
ports:
- port: 80
targetPort: 80
type: LoadBalancer
loadBalancerIP: 10.0.0.35
Apply it:
kubectl apply -f wordpress.yaml
✅ Step 6: Accessing WordPress
Once deployed:
- You can access WordPress at http://10.0.0.35 if on your LAN
- If you have a Cloudflare Tunnel set up, your domain name should now work, otherwise set up cloudflared.