This is Part 2 of my multi-arch K3s home lab series.
In Part 1, I talked about why I moved away from a single Raspberry Pi Docker setup and how I stitched together a Pi 4B, Pi 400, and an old Lenovo Ideapad into a 3-node K3s cluster.
Here, I’ll dive into the plumbing: networking, storage, remote access, and how I actually run apps like CTFd, Nextcloud, Jellyfin, qBittorrent, Syncthing, and Longhorn across the cluster.
Multi-Arch K3s Home Lab Series:
- Part 1: Why and Hardware – Why I built the cluster and the hardware lineup.
- Part 2: Networking, Storage, and Apps (you are here)
- Part 3: Failures, Lessons, and What’s Next – What broke, what I learned, and future plans.
- Part 4: Rebuilding from Scratch (The Right Way) – The laptop HDD died. So I rebuilt the cluster from scratch—this time with monitoring from day one, Helm charts, and a tutorial you can actually follow.
Networking & Ingress
Networking inside the cluster is handled mostly by K3s with its defaults, plus:
- Traefik as the built-in ingress controller to route HTTP traffic to services.
I focused on a few simple goals:
- Make sure services are reachable by stable hostnames.
- Let K3s + Traefik handle most of the heavy lifting.
- Keep the network design simple enough that I can debug it with just a terminal.
Once K3s was up, I:
- Exposed services via Kubernetes Services and Ingress objects.
- Used Traefik to route incoming traffic to the right pods.
Nothing too fancy—but it was enough to give each service a predictable URL and keep traffic flowing.
Storage: Longhorn + Laptop HDD
Storage was one of the big reasons I even built this cluster.
My goals were:
- Keep config and app data safe across restarts.
- Use the 1 TB HDD in the Lenovo Ideapad as the main bulk storage.
- Avoid hand-maintaining bind mounts on every node.
I ended up with:
- Longhorn for persistent volumes and configs.
- Longhorn abstracts storage across the nodes and handles replication.
- This means my pods can have PersistentVolumeClaims without me hardcoding host paths everywhere.
- The Ideapad’s HDD for bulk data like media files.
Initially, I considered more complex setups, like:
- A shared drive that all nodes could mount directly.
- A replicated database cluster like MariaDB Galera for central data.
But then the multi-arch reality kicked in:
- Some of the Docker images I wanted—like
mariadb-galera—were x86-only. - That meant they would only run on the laptop, not on the Pis.
Instead of fighting that, I simplified:
- Use Longhorn + node affinity to place heavy, storage-hungry workloads on the laptop.
- Let Pis handle lighter or stateless work.
In the future, I might:
- Turn the laptop (or a new box) into a proper NAS.
- Share its storage more cleanly with all nodes using something like NFS or a dedicated storage appliance.
For now, this mix of Longhorn + local HDD has been good enough and stable.
Remote Access: Tailscale + Cloudflared
I wanted to reach my cluster from outside my home network without:
- Punching a bunch of holes in my router.
- Managing complicated port-forwarding rules.
So I used a combination of:
- Tailscale – to get a private mesh VPN into my home network and cluster.
- Cloudflared – to expose specific apps to my website’s subdomains.
This gave me:
- Secure, VPN-like access for admin tasks (kubectl, dashboards, etc.).
- Clean public URLs for select services without going full “open all ports”.
It felt like the best of both worlds:
- I keep the cluster mostly behind the scenes.
- I can still share certain services with friends or my CTF team easily.
What Actually Runs on the Cluster
Here are the main services currently running on my K3s home lab:
- CTFd – for hosting CTFs for my team.
- Nextcloud – personal cloud and file sync.
- qBittorrent – for downloads.
- Jellyfin – media server.
- Syncthing – file synchronization.
- Longhorn – distributed storage for persistent volumes.
These run across:
- The Pi 4B (master).
- The Pi 400 (worker).
- The Lenovo Ideapad (worker, x86, 1 TB HDD).
Placing Workloads with Node Affinity
In practice, I don’t want every workload to land on any random node:
- Heavy services like Nextcloud, Jellyfin, and qBittorrent should live on the Ideapad.
- Lighter services and background tasks can run on the Pis.
To make that happen, I used node affinity:
- I labeled nodes and wrote simple affinity rules in my pod specs.
- This nudges K3s to:
- Prefer the laptop for CPU- and disk-heavy apps.
- Use the Pis for things that are more lightweight.
Because Longhorn handles the persistent volumes:
- I don’t have to think as much about whether a pod restarts on the exact same node.
- Longhorn takes care of making the data available where it needs to be.
I still haven’t stress-tested the entire cluster under heavy load, but under normal, everyday usage, it’s been smooth so far.
The Multi-Arch Reality Check
One of the earliest real-world lessons:
- Not every Docker image supports both ARM and x86.
I ran into this when:
- I wanted to use MariaDB Galera as a central database cluster.
- The available images were x86-only.
Result:
- They worked on the Ideapad.
- They wouldn’t run on the Pis at all.
This forced a few changes:
- I dropped that idea (for now).
- I started thinking carefully about:
- Which services must run on x86.
- Which ones can be spread across ARM + x86.
It was a good reminder:
- A multi-arch cluster is powerful, but you still have to respect what each CPU architecture can actually run.
Learning to Deploy Docker Images to K3s
The next big learning curve wasn’t hardware—it was Kubernetes YAML.
My background before this:
- Run things with
docker run .... - For slightly more complex setups, use
docker-compose.
With K3s, that turned into:
- Writing Deployments, Services, and Ingress definitions.
- Figuring out how to:
- Reference Docker images correctly.
- Expose ports properly.
- Mount volumes for configs and data.
The internet had a lot of Kubernetes content, but:
- There weren’t many Pi-focused, K3s-specific beginner guides for exactly what I was doing.
So I learned by:
- Trial and error.
- Checking logs a lot.
- Spamming
kubectl get pods -Auntil I understood what was happening.
Over time:
- My manifests got cleaner.
- I understood what each piece did.
- Deploying a new container to the cluster went from “painful” to “kind of fun”.
When the Pi’s Wi‑Fi Died
Not all issues were software.
At one point, the internal Wi‑Fi card on the Pi 4B (my master node) started failing:
- It wouldn’t connect properly to the network.
My workaround:
- I plugged in an external USB Wi‑Fi NIC.
- Set up a static IP on the new interface.
But then:
- The K3s server still refused to behave properly.
- It felt like something in the stack was stuck on the old
wlan0interface instead of the newwlan1.
That kicked off a debugging session:
- Reading documentation.
- Asking chatbots questions.
- Digging through forum threads.
- Cleaning up old references and making sure both the OS and K3s agreed on which interface was “real”.
It wasn’t glamorous, but it was a great operations lesson:
- When underlying network assumptions change, your cluster absolutely notices.
Operating the Cluster Day-to-Day
Right now, my operations style is very CLI-first:
kubectl get nodes– to check if all nodes are ready.kubectl get pods -A– to see what’s running and if any pods are stuck.- Directly checking logs when something seems off.
I haven’t yet deployed a full central monitoring stack like:
- Prometheus + Grafana.
- Loki.
- Uptime Kuma.
Why?
- So far, the cluster has been stable enough that I haven’t needed dashboards to keep it alive.
But I’m planning to add monitoring and logging soon, mainly to:
- Spot problems before users notice them.
- Visualize resource usage across nodes.
- Understand how the cluster behaves under load or during failures.
What’s Next – Part 3: Lessons and Future Plans
In Part 3, I’ll step back from the technical wiring and talk about:
- What this home lab taught me about:
- Managing multiple devices in sync.
- Learning networking and infrastructure through practice.
- Relying on containers and K3s as a safety net.
- What I’d do differently if I rebuilt this cluster today:
- Where Proxmox might fit.
- How I’d handle backups and logging from day one.
- How this setup fits into my CTF journey and future experiments.
If you haven’t checked it out yet, the cluster config lives here: