← Back to projects

FLAGSHIP HOME LAB CASE STUDY

Segmented Kubernetes Home Lab with GitOps and Zero Trust Access

I built this environment to practice the problems that make infrastructure engineering real: network boundaries, controlled administration, Kubernetes application delivery, persistent storage, DNS, monitoring, and failure recovery. The most important result was not installing another platform. It was finding an alternate path around my intended firewall boundary, correcting the architecture, and turning the lab into a system I can explain, validate, and continue improving.

Status: Operational / Expanding Platform: Proxmox VE Role: Architect / Builder / Operator Focus: Segmentation, GitOps, Recovery Context: Personal Engineering Lab
EVIDENCE AT A GLANCE 3-node Kubernetes ~5.5-hour initial cluster build 132 Cilium checks passed Argo CD + Helm GitOps Segmentation bypass corrected

I wanted one lab that connects infrastructure, security, and platform engineering.

The lab began as a Proxmox environment for virtual machines and remote administration. It grew into a segmented platform containing an OPNsense firewall, an identity-aware access path, a three-node Kubernetes cluster, GitOps-managed services, centralized DNS, monitoring, and isolated security ranges.

My design goal is not to make a home lab look like a large enterprise. It is to reproduce the same engineering questions at a smaller scale: where trust boundaries exist, how changes are controlled, what happens when a dependency fails, and whether another operator could understand the system.

Public access, administration, and protected workloads are separate paths.

flowchart TB
  USER[Remote Operator]
  SITE[Public Portfolio
Cloudflare Pages] ACCESS[Cloudflare Access] TUNNEL[Outbound Cloudflare Tunnel] BASTION[Bastion
Guacamole] FIREWALL[OPNsense
Policy Enforcement] WIN[Windows Jump Workstation] K8S[Three-Node Kubernetes Cluster] DNS[Pi-hole DNS] RANGE[Isolated Security Range] STATUS[Public Read-Only Status Page] USER --> SITE USER --> ACCESS ACCESS --> TUNNEL TUNNEL --> BASTION BASTION --> FIREWALL FIREWALL --> WIN FIREWALL --> K8S FIREWALL --> DNS FIREWALL --> RANGE K8S --> STATUS

Public presentation

The portfolio is hosted independently through Cloudflare Pages. The website remains available even when the home lab is offline.

Identity-aware access

Cloudflare Access authenticates the operator before traffic reaches the private application path. The tunnel connector initiates its connection outbound, so administrative services do not require direct WAN forwarding.

Policy enforcement

OPNsense sits between the upstream network and the protected lab network. Access from the bastion to protected systems requires an explicit firewall rule.

Protected workloads

Kubernetes, management workstations, DNS, and internal applications sit behind the firewall boundary. Intentionally vulnerable workloads use a separate security range.

The original bastion design created an alternate path around the firewall.

The bastion originally had one interface on the upstream network and another on the protected lab network. That made remote administration convenient, but it also allowed the bastion to reach protected workloads directly. Traffic did not have to traverse the OPNsense policy boundary.

flowchart LR
  CF[Cloudflare Tunnel] --> B[Dual-Homed Bastion]
  B --> UP[Upstream Network]
  B -. Direct Path .-> LAB[Protected Lab Network]
  FW[OPNsense Firewall] --> LAB
        

This was not a direct internet exposure or a data leak. It was an internal segmentation bypass: the architecture contained an alternate routing path that defeated the intended enforcement point.

01. Removed the protected-side interface
The bastion became single-homed on the upstream network.

02. Routed lab traffic through OPNsense
A persistent route sends protected-network traffic to the firewall instead of directly to the destination network.

03. Added explicit policy
Firewall rules permit only the required bastion-to-service paths.

04. Revalidated the applications
Guacamole, Windows RDP, and Uptime Kuma were tested through the enforced path, then the route and tunnel were reboot-tested.

flowchart LR
  CF[Cloudflare Tunnel] --> B[Single-Homed Bastion]
  B --> FW[OPNsense Firewall]
  FW --> WIN[Windows RDP]
  FW --> KUMA[Uptime Kuma]
  FW --> LAB[Protected Services]
        

The cluster is operated as a small platform, not a collection of joined nodes.

The Kubernetes environment uses one control-plane node and two workers. Cilium provides cluster networking. Argo CD reconciles the desired state from Git, and Helm is the default packaging method for platform services.

Cilium networking

Cilium was selected because it aligns with the platform networking model I am building experience with professionally. The deployment was validated with the Cilium connectivity test suite.

Argo CD GitOps

Argo CD uses an app-of-apps structure to manage namespaces, infrastructure components, and applications. Git holds the intended state and Argo CD detects and reconciles drift.

Helm packaging

Helm charts provide repeatable deployments and configurable defaults. Argo CD renders and applies the charts, rather than relying on one-time manual installation commands.

Persistent storage

Rancher Local Path Provisioner supplies dynamic node-local volumes. It is intentionally simple and functional, but it is not treated as replicated or highly available storage.

Git is the control point for adding and changing cluster services.

flowchart LR
  CHANGE[Configuration Change] --> GIT[Git Commit]
  GIT --> ROOT[Argo CD Root Application]
  ROOT --> APP[Child Application]
  APP --> HELM[Helm Chart or Manifests]
  HELM --> K8S[Kubernetes Resources]
  K8S --> HEALTH[Health and Sync Validation]
        

The Local Path Provisioner is a clear example of this model. I deployed the Rancher local-path-provisioner Helm chart, pinned to version 0.0.36, through Argo CD. The chart generated its ServiceAccount, ClusterRole, ClusterRoleBinding, Deployment, ConfigMap, and StorageClass. I configured local-path as the cluster default.

Uptime Kuma then requested a 2 Gi persistent volume. The claim binding successfully proved the GitOps, RBAC, storage provisioning, and application persistence path together.

Each service is being used to prove part of the operating model.

Uptime Kuma

Uptime Kuma runs in Kubernetes with persistent storage. A public, read-only status page is separated from the administrative interface, which remains behind Cloudflare Access.

Pi-hole

Pi-hole provides household DNS filtering and forwards allowed queries to Quad9. Deploying it exposed a real availability concern: centralized DNS becomes a dependency for the entire household.

Apache Guacamole

Guacamole provides browser-based access to internal workstations. It runs with guacd and PostgreSQL on the bastion and is reached through the outbound tunnel path.

Windows jump workstation

The Windows workstation is single-homed inside the protected network. Remote access reaches it through Cloudflare, Guacamole, and an explicit OPNsense rule instead of a direct RDP exposure.

The design favors controlled paths and narrow permissions.

Single-homed bastion
Removes the direct path into the protected network and preserves the firewall as the enforcement point.

Outbound-only tunnel
The connector establishes the Cloudflare Tunnel from inside the network. No direct inbound management port is required.

Layered authentication
Cloudflare Access validates identity before the operator reaches Guacamole or the destination operating system.

Firewall allow rules
Protected services are reached through explicit source, destination, and port policy instead of broad network access.

Git-reviewed state
Cluster configuration is stored in Git and reconciled by Argo CD, making intended changes visible and repeatable.

Chart-provided RBAC
Infrastructure charts provide their required permissions. Namespace-scoped bindings remain the preferred model for ordinary applications.

A power outage showed which dependencies were operationally important.

After the Proxmox host restarted, the environment did not return as one indivisible system. Networking, the Kubernetes API, kubelet state, the tunnel, application containers, and DNS each had their own startup and validation requirements.

The outage made several next steps concrete: establish VM startup order, verify that containerd and kubelet start automatically, keep swap disabled across reboots, validate the API server before troubleshooting applications, and document a repeatable health check.

Pi-hole also demonstrated that availability is not the same as connectivity. The internet connection can remain operational while failed DNS makes the household appear offline. A second resolver on separate hardware is now part of the resilience plan.

The lab is functional, but the remaining risks are documented instead of hidden.

Single DNS resolver

Pi-hole is currently a single point of failure. A secondary Pi-hole on a different physical system is the preferred correction.

Node-local storage

Local Path Provisioner does not replicate application data. A failed node can make its local volume unavailable.

Backup coverage

Git can reconstruct configuration, but application data, etcd state, firewall configuration, and DNS configuration require separate backups.

Secrets workflow

The GitOps repository still needs a formal encrypted-secrets workflow. SOPS with age is the leading option for the next phase.

The next work is recoverability and platform maturity, not another random service.

01. Recovery validation
Finalize Proxmox startup order and document the post-outage validation sequence.

02. Backups
Automate etcd snapshots and back up application data, Pi-hole, and OPNsense outside the primary host.

03. DNS resilience
Add a second resolver on separate hardware so one VM or host failure does not remove household name resolution.

04. Secrets management
Add SOPS and age so sensitive Kubernetes configuration can remain encrypted in Git.

05. Gateway and TLS
Implement Envoy Gateway and cert-manager after the recovery baseline is complete.

06. Observability
Add Prometheus, Grafana, and Alertmanager for cluster metrics and actionable alerts.

The strongest outcome is an architecture that became safer after being challenged.

I built and operated a segmented Proxmox and Kubernetes environment using OPNsense, Cloudflare Access and Tunnel, Guacamole, Cilium, Argo CD, Helm, dynamic storage, monitoring, and centralized DNS. During implementation, I identified a dual-homed bastion that bypassed the intended firewall path, redesigned the access flow, and validated the corrected route through application and reboot testing.

The lab now provides a practical foundation for infrastructure engineering, platform operations, security architecture, GitOps, troubleshooting, and recovery work. It is intentionally unfinished, but its remaining limitations are understood and sequenced into the next engineering phases.