Migrating Self-Hosted Stack to Kubernetes Notes (RKE2)
A Comprehensive Guide to RKE2 Setup, Data Migration, and App Deployment
Transitioning from a traditional Docker Compose setup to a fully-fledged Kubernetes cluster is a major milestone for any self-hosting enthusiast or platform engineer. This guide walks you through the entire process from setting up a lightweight, production-ready RKE2 cluster to safely migrating persistent databases and managing deployments via Helm.
Step 1: Setting up the RKE2 Cluster
RKE2 (Rancher Kubernetes Engine 2) is a hardened, CNCF certified Kubernetes distribution that doesn't rely on Docker. Instead, it uses containerd as its runtime. Getting it running on a fresh Ubuntu machine is surprisingly straightforward.
# 1. Download and run the RKE2 installation script
curl -sfL https://get.rke2.io | sh -
# 2. Enable and start the RKE2 server service
systemctl enable rke2-server.service
systemctl start rke2-server.service
# 3. Add kubectl to your path
echo 'export PATH=$PATH:/var/lib/rancher/rke2/bin' >> ~/.bashrc
source ~/.bashrc
# 4. Set up kubeconfig
export KUBECONFIG=/etc/rancher/rke2/rke2.yamlStep 2: Safely Migrating Persistent Data (The BusyBox Trick)
When migrating applications like Grafana, n8n, and MySQL, simply copying the live files can result in corrupted databases (like SQLite's Unexpected EOF in archive error). The safest way to backup Docker volumes is to stop the container and use a clean busyboxenvironment.
# Stop the active container first to prevent database writes
docker stop grafana
# Use busybox to cleanly archive the volume
docker run --rm -v your_volume_name:/data -v $(pwd):/backup busybox tar -
czvf /backup/app_data_backup.tar.gz -C /data .After transferring the .tar.gz to your new server, extract it to a HostPath directory (e.g., /opt/ app-data ). Crucially, you must fix the Linux file ownership so the Kubernetes container can read it!
Grafana: Runs as UID 472 . Run: chown -R 472:472 /opt/grafana-data
n8n: Runs as UID 1000 . Run: chown -R 1000:1000 /opt/n8n-data
Step 3: Application Deployment Strategies
- Grafana: Security Settings and iFrames
When embedding Grafana dashboards into a React app using an iframe, modern browsers will block the cookies due to cross-site security rules. You must pass specific environment variables to
the Grafana pod to relax these settings.
env:
- name: GF_SECURITY_ALLOW_EMBEDDING
value: "true"
- name: GF_SECURITY_COOKIE_SAMESITE
value: "none"
- name: GF_SECURITY_COOKIE_SECURE
value: "true"
- name: GF_AUTH_ANONYMOUS_ENABLED
value: "true"- Ghost Blog & MySQL: The Password Trap
Ghost requires a MySQL database. When you migrate an existing MySQL data folder, the original password is hardcoded into the database files. Changing the MYSQL_ROOT_PASSWORD environment variable in Kubernetes will not change an existing database password. To secure your database after migration, you must exec into the running pod:
kubectl exec -it deployment/mysql -- mysql -u root -p
# Inside the MySQL prompt:
ALTER USER 'root'@'%' IDENTIFIED BY 'NewStrongPassword!';
FLUSH PRIVILEGES;- React Static Apps & ImagePullBackOff.
If you build a local Docker image for a custom React app, Kubernetes will throw an ImagePullBackOff error. This is because K3s/RKE2 use containerd , which has a completely separate image cache from Docker. To fix this, export the image from Docker and import it into containerd:
docker save my-react-app:latest > app.tar
# If using K3s:
k3s ctr images import app.tar
# If using RKE2:
/var/lib/rancher/rke2/bin/ctr images import app.tarAlways ensure your pod YAML specifies imagePullPolicy: Never or IfNotPresent so Kubernetes doesn't try to look online for it.
Step 4: The Capstone - Installing Rancher
Managing raw YAML files is great, but Rancher provides a world-class UI to manage your cluster, view logs, and scale workloads. We deploy it using Helm.
# 1. Install Helm
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/
scripts/get-helm-3
bash get_helm.sh
# 2. Add the Rancher repo and create namespace
helm repo add rancher-stable https://releases.rancher.com/server-charts/stable
kubectl create namespace cattle-system
# 3. Install Rancher via Helm
helm install rancher rancher-stable/rancher --namespace cattle-system --set
hostname=rancher.yourdomain.com --set replicas=1 --set
bootstrapPassword=YourSecurePassword123! --set ingress.ingressClassName=nginx
--set ingress.tls.source=secret --set ingress.extraAnnotations."cert-
manager\.io/cluster-issuer"=letsencrypt-prod