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.