A lot has changed since I first wrote about building my homelab with AI assistance. What started as a learning project has evolved into a production-grade platform spanning two repositories, multiple Terraform projects, a full GitOps pipeline, and services running on both Docker and Kubernetes. This post is both a progress update and a visual tour of where things stand today.
The Big Picture
My homelab now runs across:
- Proxmox cluster โ 2 physical nodes hosting all VMs
- Docker hosts โ 2 VMs running containerized services (media, networking, Gitea, etc.)
- K3s cluster โ 2 control plane nodes + workers for Kubernetes workloads
- VyOS router โ Software-defined networking with BGP
- TrueNAS โ Network storage
- Oracle Cloud VPS โ Pangolin tunnel endpoint for external access
Everything is defined in code across two repos:
| Repo | Purpose |
|---|---|
| homelab | Terraform (VM provisioning), Ansible (config management), Docker Compose stacks |
| gitops | ArgoCD applications, Kubernetes manifests, Helm values |
Infrastructure Layer
Terraform Projects
I manage five separate Terraform projects, each with its own state:
infrastructure/
โโโ terraform/ # Docker host VMs
โโโ terraform-k3s/ # K3s cluster VMs
โโโ terraform-vyos/ # VyOS router VM
โโโ terraform-truenas/ # TrueNAS VM
โโโ terraform-authentik/ # Authentik identity provider
โโโ terraform-grafana/ # Grafana Cloud dashboards
The reusable proxmox-vm module handles common patterns: cloud-init, networking, SSH keys. Each project just defines the specifics.
Ansible Automation
Ansible handles everything post-provisioning:
- Docker installation and configuration
- Stack deployment โ copies Compose files, manages
.env, runsdocker compose up -d - K3s cluster upgrades โ rolling upgrades across control plane and workers
- Tailscale setup โ for remote access to the homelab network
The key playbook is deploy-stacks.yml, which takes a list of stacks and deploys them idempotently. Terraform calls this automatically after VM creation.
Local CI/CD
One of the most useful additions: git hooks that auto-deploy on commit. When I commit changes to a stack, the hook detects what changed and runs the appropriate Ansible deployment.
git commit -m "Update traefik config"
# โ Automatically detects and deploys changed stacks
No manual commands, no forgetting to deploy. Just commit and push.
Docker Stacks
The homelab repo defines 10+ Docker Compose stacks:
| Stack | Services |
|---|---|
| core | Traefik (reverse proxy), Portainer, node-exporter, cAdvisor, pve-exporter |
| media | Jellyfin, Sonarr, Radarr, Prowlarr, qBittorrent, Gluetun VPN |
| database | PostgreSQL |
| networking | Pi-hole (DNS), Netbox (IPAM) |
| gitea | Gitea server, container registry, CI runner |
| ai-tools | Open WebUI + Ollama (local LLM) |
| projects | Vikunja (project & task tracking) |
| newt | Pangolin tunnel connector |
| pangolin | VPS-side tunnel control plane |
| monitoring-vps | Prometheus, Grafana, Loki on VPS |
| authentik-vps | Authentik identity provider on VPS |
| crowdsec-vps | CrowdSec security on VPS |
| pihole-vps | Pi-hole on VPS for external DNS |
All stacks share a common env file and follow the same patterns: Traefik labels for routing, consistent logging, health checks.
Kubernetes (GitOps)
The K3s cluster runs workloads managed entirely through ArgoCD. The gitops repo contains:
ArgoCD Applications
apps/
โโโ bootstrap.yaml # Root app that deploys everything else
โโโ argocd.yaml # ArgoCD self-management
โโโ cilium.yaml # CNI with BGP
โโโ traefik.yaml # Ingress controller
โโโ cloudflare-tunnel.yaml # Secure external access
โโโ monitoring.yaml # Prometheus + Grafana
โโโ loki.yaml # Log aggregation
โโโ longhorn.yaml # Distributed storage
โโโ uptime-kuma.yaml # Status monitoring
โโโ ntfy.yaml # Push notifications
โโโ vaultwarden.yaml # Password manager
โโโ gitea-runner.yaml # CI runner for Gitea
โโโ newt.yaml # Pangolin tunnel client
Key Components
- Cilium CNI โ Replaced Flannel; provides BGP peering with VyOS for LoadBalancer IPs
- Cloudflare Tunnel โ Subnet routing (not hostname routing) for scalable external access
- Tailscale + Cloudflare Access โ Access restricted to Tailscale IPs only
- Full observability โ Prometheus scrapes Docker hosts, K3s, and Proxmox; Loki aggregates logs; Grafana dashboards
kubectl Anywhere
I can access the cluster from anywhere using three kubeconfig options:
- Direct โ On the home network, connect directly to control plane
- SSH tunnel โ Port-forward through VyOS when on home network but different subnet
- Cloudflare tunnel โ Works from anywhere with Tailscale connected
Networking
VyOS Router
The VyOS VM handles:
- Inter-VLAN routing between lab network (10.10.10.0/24) and home network
- BGP peering with Cilium for Kubernetes LoadBalancer IPs
- Static routes for Kubernetes pod and service networks
Cloudflare Tunnel + Tailscale
External access uses subnet routing instead of individual hostname routes:
- Routes
10.42.0.0/16(pod network) and10.43.0.0/16(service network) through the tunnel - Cloudflare Access policy restricts access to Tailscale IPs (
100.64.0.0/10) - No need to configure individual services โ just add them to the cluster
This pattern scales without tunnel configuration changes.
Pangolin Tunnel
For services that need public access (without Tailscale), I run Pangolin โ a self-hosted tunnel solution:
- Control plane runs on Oracle Cloud VPS
newtconnector runs on Docker host and K3s- Provides stable public endpoints for specific services
Observability
Monitoring Stack
- Prometheus โ Scrapes metrics from Docker hosts, K3s, Proxmox, and individual services
- Grafana โ Dashboards for everything; some managed via Terraform
- Loki โ Log aggregation from Docker and Kubernetes
- Alertmanager โ Routes alerts to ntfy for push notifications
- Uptime Kuma โ External health checks and status page
Exporters
The core stack runs exporters for comprehensive coverage:
node-exporterโ Host-level metricscAdvisorโ Container metricspve-exporterโ Proxmox metrics
What’s Changed Since Last Time
Since my previous homelab post, I’ve added:
- VPS integration โ Pangolin, CrowdSec, Authentik, monitoring all running on Oracle Cloud
- Full monitoring pipeline โ Prometheus + Loki + Grafana with alerting to ntfy
- Longhorn storage โ Distributed block storage for K3s stateful workloads
- Uptime Kuma + Vaultwarden + Ntfy โ Self-hosted alternatives to SaaS tools
- Terraform for Authentik and Grafana โ Managing identity and dashboards as code
- K3s upgrade automation โ Ansible playbook for rolling cluster upgrades
- Local CI/CD with git hooks โ Auto-deploy on commit
The platform has gone from “learning project” to “I actually rely on this daily.”
Lessons Learned
GitOps is worth the setup cost. Once ArgoCD is running, adding services is just a YAML file and a commit.
Subnet routing > hostname routing. Configuring individual hostnames doesn’t scale. Route the subnet and use DNS.
Observability from day one. I can debug issues in minutes because I have metrics, logs, and traces.
Terraform state management matters. Multiple smaller projects with isolated state beats one giant project.
Document as you go. The
CLAUDE.mdfiles and READMEs save me constantly when I come back to something after weeks.VPS extends homelab reach. A free Oracle Cloud instance gives me public endpoints and redundancy without exposing my home IP.
What’s Next
- Authentik SSO โ Single sign-on across all services
- Backup automation โ TrueNAS snapshots + off-site replication
- More K3s workloads โ Migrate remaining Docker services that benefit from HA
- Cost tracking โ Power monitoring and cloud cost dashboards
If you’re building a homelab, I’d encourage you to treat it like a real platform: version control everything, automate deployments, and invest in observability early. The compound benefits are real โ every new service is easier to add, debug, and maintain.