After years of recommending cloud architectures to clients, I arrived at a simple conclusion: you can't speak with authority about Kubernetes, networking, or resilience if you've never operated anything outside a managed console. My homelab was born out of that need — a place to break things without it costing anyone money but me.
The hardware
Nothing exotic. The current setup runs on:
- A mini-PC with 32 GB of RAM and an 8-core processor as the main node.
- Two Raspberry Pi 4s as additional worker nodes, useful for testing ARM architectures.
- A NAS with RAID 1 for persistent storage and backups.
- A managed switch with VLANs to separate management traffic from workload traffic.
The deliberate decision was not to over-invest in hardware. The goal isn't to have a personal datacenter, it's to have a realistic environment where networking, storage, and orchestration issues behave the way they would in production, even at minimal scale.
Foundation: Linux, with no intermediate abstractions
The main node runs Debian 12 without any desktop virtualization layer. Everything is managed via
SSH and systemd. This decision is intentional: I want to face the same problems I'd face
managing a server on AWS or in my own datacenter — user management, permissions, updates,
firewalls with nftables, and observability with standard tools like journalctl and
node_exporter.
# Ejemplo real de mi configuración de nftables para el nodo principal
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif lo accept
tcp dport 22 ip saddr 192.168.10.0/24 accept
tcp dport { 80, 443 } accept
ip protocol icmp accept
}
}
Docker first, Kubernetes when the problem justifies it
A common mistake when building a homelab is jumping straight to Kubernetes because "that's what the industry uses." I start almost everything in Docker Compose. If the service doesn't need horizontal scaling, automatic failover, or declarative management of multiple replicas, Compose is simpler to operate and debug:
# docker-compose.yml — stack de observabilidad local
services:
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- '9090:9090'
grafana:
image: grafana/grafana:latest
depends_on:
- prometheus
ports:
- '3000:3000'
volumes:
- grafana-data:/var/lib/grafana
volumes:
grafana-data:
When a use case does justify real orchestration — for example, simulating a service failover when a node goes down — I migrate that specific workload to a k3s cluster, the lightweight Kubernetes distribution that runs comfortably on the Raspberry Pis:
curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644
kubectl get nodes -o wide
What I've learned running this
The most valuable part hasn't been any specific technology, but the operational friction that
only shows up when you're responsible for the full lifecycle: updating a kernel and having to fix
a broken network driver, losing a NAS disk and running an actual restore from backup, or debugging
why a pod stays stuck in CrashLoopBackOff with no managed dashboard to explain it for you.
That kind of hands-on experience is what later translates into more honest architectural decisions at work: when Kubernetes' complexity is actually worth it, when a simple service on ECS is enough, and why observability isn't an extra — it's the first thing you need to install.