[{"content":"This guide documents how to create Proxmox VMs using Terraform for use as Kubernetes nodes in a k3s cluster. The Terraform configuration clones a cloud-init template, assigns fixed IPs and SSH keys, and tags VMs for k3s roles so they can be targeted by k3s-ansible or similar automation.\nOverview # The setup provisions two groups of VMs:\nk3s masters (3 VMs): control-plane nodes for the Kubernetes cluster k3s workers (2 VMs): worker nodes for running workloads All VMs are created by full clone from a cloud-init template (VM ID 9999 in the examples). They receive static IPs, a common SSH user and public key, and Proxmox tags (k3s, master or worker) for identification. After Terraform applies, you deploy k3s onto these VMs using Ansible or another method. This guide uses placeholders like \u0026lt;subnet\u0026gt; and \u0026lt;first-master-ip\u0026gt;; replace them with values that match your network.\nPrerequisites # Proxmox VE host with API access (e.g. https://your-proxmox-host:8006) Cloud-init template VM (ID 9999 in this guide). Create it first using the Proxmox cloud-init template guide Terraform \u0026gt;= 1.0 SSH key pair for the cloud user (e.g. ~/.ssh/pvm-ubuntu-cloud and ~/.ssh/pvm-ubuntu-cloud.pub). The same key is used later to fetch kubeconfig and run Ansible Proxmox credentials (username/password; API token can be used when the provider supports it) Architecture # Role VM names Count IP range (example) Tags Master k3s-master-01 … 03 3 \u0026lt;subnet\u0026gt;.201–203/24 k3s, master Worker k3s-worker-01 … 02 2 \u0026lt;subnet\u0026gt;.211–212/24 k3s, worker Proxmox node: your Proxmox host name (set in Terraform; e.g. proxmox-pve or a variable) Network: one bridge (e.g. vmbr0), one virtio NIC per VM Gateway: your LAN gateway (e.g. \u0026lt;subnet\u0026gt;.1) Replace \u0026lt;subnet\u0026gt; with your actual subnet (e.g. 192.168.1). Adjust IP ranges, node name, and bridge in the Terraform variables or in main.tf to match your environment.\nFile layout # The Terraform code lives in the homelab repo under kubernetes/terraform/:\nFile Purpose main.tf Proxmox VM resources: k3s masters and workers (clone, CPU, memory, network, cloud-init) providers.tf Terraform and Proxmox provider (bpg/proxmox) configuration variables.tf Input variables: Proxmox endpoint, username, password terraform.tfvars.example Example values; copy to terraform.tfvars and fill in Provider and variables # Provider: bpg/proxmox (~\u0026gt; 0.66 in the snippet below) Authentication: endpoint URL, username, and password (sensitive). The code comment indicates a future switch to an API token. Variables:\nVariable Description Example proxmox_endpoint Proxmox API URL https://your-proxmox-host:8006 proxmox_username Proxmox user root@pam or your-username@pam proxmox_password Proxmox password (sensitive) Step-by-step usage # 1. Clone the repo and enter the Terraform directory # cd /path/to/homelabs cd kubernetes/terraform 2. Copy and edit tfvars # cp terraform.tfvars.example terraform.tfvars Edit terraform.tfvars and set:\nproxmox_endpoint — your Proxmox API URL (e.g. https://\u0026lt;proxmox-host\u0026gt;:8006) proxmox_username — e.g. root@pam proxmox_password — your Proxmox password Do not commit terraform.tfvars; add it to .gitignore if needed.\n3. Ensure the cloud-init template and SSH key exist # Template VM ID 9999 must exist and be a full cloneable template (see Proxmox cloud-init template). Place the public key at ~/.ssh/pvm-ubuntu-cloud.pub (or update the file() path in main.tf). 4. Initialize and apply # terraform init terraform plan terraform apply Confirm when prompted. Terraform will create 3 master and 2 worker VMs on the configured Proxmox node.\n5. Deploy k3s on the new VMs # Use your preferred method (e.g. k3s-ansible) with the same SSH user and key (ansibleuser, ~/.ssh/pvm-ubuntu-cloud).\nRunning the Ansible playbooks Copying kubeconfig from the first master (replace \u0026lt;first-master-ip\u0026gt; with your master’s IP):\nscp -i ~/.ssh/pvm-ubuntu-cloud ansibleuser@\u0026lt;first-master-ip\u0026gt;:~/.kube/config ~/.kube/config Verifying with kubectl get pods --all-namespaces Destroying the VMs # To remove all Terraform-managed k3s VMs:\ncd kubernetes/terraform terraform destroy Confirm when prompted. This does not remove the template VM (9999) or any resources not managed by this Terraform configuration.\n","externalUrl":null,"permalink":"/docs/k3s/","section":"Documentation","summary":"This guide documents how to create Proxmox VMs using Terraform for use as Kubernetes nodes in a k3s cluster. The Terraform configuration clones a cloud-init template, assigns fixed IPs and SSH keys, and tags VMs for k3s roles so they can be targeted by k3s-ansible or similar automation.\nOverview # The setup provisions two groups of VMs:\nk3s masters (3 VMs): control-plane nodes for the Kubernetes cluster k3s workers (2 VMs): worker nodes for running workloads All VMs are created by full clone from a cloud-init template (VM ID 9999 in the examples). They receive static IPs, a common SSH user and public key, and Proxmox tags (k3s, master or worker) for identification. After Terraform applies, you deploy k3s onto these VMs using Ansible or another method. This guide uses placeholders like \u003csubnet\u003e and \u003cfirst-master-ip\u003e; replace them with values that match your network.\n","tags":"","title":"K3s","type":"docs"},{"content":"This guide documents how to install a new Proxmox VE node, perform the first login, join it to an existing cluster, and configure the no-subscription repositories.\nCreate the installation USB # Download the desired Proxmox VE ISO from https://www.proxmox.com/en/downloads/proxmox-virtual-environment/iso. Use Rufus to burn the ISO to a USB stick. When Rufus prompts for the image mode, choose DD Image mode instead of ISO mode. Install Proxmox VE # Boot the target machine from the Proxmox USB stick. Keep the machine connected to ethernet during installation so the installer can detect the correct network. In the installer: Enter your email address when prompted. Set the hostname to e.g. pve-ms-01.local. Confirm that the IP address matches the one identified in your network for this node; normally it is auto-detected correctly. For laptops, avoid closing the lid during installation to prevent sleep or suspend. Complete the installer and reboot into Proxmox VE. First login to the web interface # From another machine on the same network, open:\nhttps://\u0026lt;proxmox-ip\u0026gt;:8006 At the login screen:\nUser: root Realm: Linux PAM standard authentication Password: The one set in the previous step After logging in, acknowledge an initial warning (e.g. about subscription or no valid cluster) and proceed.\nJoin the node to an existing cluster # On the new node, open a shell (either via the web UI or SSH) and run:\npvecm add CLUSTER_IP Replace CLUSTER_IP with the IP address of any existing Proxmox node in the cluster. Follow the prompts to join; afterwards you can verify with:\npvecm status Allow Synology NFS access # Add this node\u0026rsquo;s IP address to the Synology NFS permissions so it can access the synology-nfs storage:\nOn the Synology: Go to Control Panel \u0026gt; Shared Folder. Select the folder dedicated to store Proxmox VMs, e.g. proxmox-vm and click Edit. Adjust the NFS permissions to include the new node\u0026rsquo;s IP address. Add the node to the HA group # In the Proxmox web UI, go to Datacenter \u0026gt; HA \u0026gt; Groups. Edit the existing HA group and add the new node. Save the changes so the node can participate in high availability. Change the root password # After the node is joined and accessible:\nOpen a shell on the node (via the Proxmox web UI or SSH).\nRun:\npasswd Enter and confirm the new root password.\nSave it in your password manager.\nProxmox VE no-subscription repository setup # By default, Proxmox VE enables enterprise repositories that require a subscription. Without a subscription, running:\napt-get update will return 401 Unauthorized errors. To fix this, configure the no-subscription repositories by editing two files. Replace trixie with your Debian codename (check with:\nlsb_release -cs ).\n/etc/apt/sources.list.d/pve-enterprise.sources # # /etc/apt/sources.list.d/pve-enterprise.sources Types: deb URIs: http://download.proxmox.com/debian/pve Suites: trixie Components: pve-no-subscription Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg /etc/apt/sources.list.d/ceph.sources # # /etc/apt/sources.list.d/ceph.sources Types: deb URIs: http://download.proxmox.com/debian/ceph-squid Suites: trixie Components: no-subscription Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg After saving both files, run:\napt-get update apt-get dist-upgrade to refresh package lists and apply the latest updates from the no-subscription repositories.\n","externalUrl":null,"permalink":"/docs/proxmox-installation/","section":"Documentation","summary":"This guide documents how to install a new Proxmox VE node, perform the first login, join it to an existing cluster, and configure the no-subscription repositories.\nCreate the installation USB # Download the desired Proxmox VE ISO from https://www.proxmox.com/en/downloads/proxmox-virtual-environment/iso. Use Rufus to burn the ISO to a USB stick. When Rufus prompts for the image mode, choose DD Image mode instead of ISO mode. Install Proxmox VE # Boot the target machine from the Proxmox USB stick. Keep the machine connected to ethernet during installation so the installer can detect the correct network. In the installer: Enter your email address when prompted. Set the hostname to e.g. pve-ms-01.local. Confirm that the IP address matches the one identified in your network for this node; normally it is auto-detected correctly. For laptops, avoid closing the lid during installation to prevent sleep or suspend. Complete the installer and reboot into Proxmox VE. First login to the web interface # From another machine on the same network, open:\n","tags":"","title":"Proxmox Installation","type":"docs"},{"content":"This guide installs and operates Rancher on an existing Kubernetes cluster using Helm and cert-manager.\nOfficial docs: Install/Upgrade Rancher on a Kubernetes Cluster\nPrerequisites # Kubernetes cluster (K3s, RKE2, EKS, or any supported distribution) kubectl and Helm installed Installation # 1. Add Helm repos # helm repo add rancher-stable https://releases.rancher.com/server-charts/stable helm repo update 2. Create namespace # kubectl create namespace cattle-system 3. Install cert-manager # Required for Rancher-generated TLS certificates (default). Skip if you use your own certificates (ingress.tls.source=secret) or TLS termination on an external load balancer.\nCRDs are applied separately so they are not removed on helm uninstall; use crds.enabled=false so Helm does not try to manage them.\nkubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.19.3/cert-manager.crds.yaml helm repo add jetstack https://charts.jetstack.io helm repo update helm install cert-manager jetstack/cert-manager \\ --namespace cert-manager \\ --create-namespace \\ --set crds.enabled=false 4. Install Rancher # Use one of the methods below. Set hostname to the DNS name that points to your cluster (e.g. load balancer, node, or Cloudflare Tunnel). If you use a Cloudflare Tunnel, use Rancher-generated certs or certificates from files; Let\u0026rsquo;s Encrypt HTTP-01 is usually not suitable behind a tunnel.\nRancher-generated certificates (default):\nhelm install rancher rancher-latest/rancher \\ --namespace cattle-system \\ --set hostname=rancher.my.org \\ --set bootstrapPassword=\u0026#39;\u0026lt;CHANGE_ME\u0026gt;\u0026#39; Let\u0026rsquo;s Encrypt:\nhelm upgrade --install rancher rancher-latest/rancher \\ --namespace cattle-system \\ --set hostname=rancher.my.org \\ --set bootstrapPassword=\u0026#39;\u0026lt;CHANGE_ME\u0026gt;\u0026#39; \\ --set ingress.tls.source=letsEncrypt \\ --set letsEncrypt.email=me@example.org \\ --set letsEncrypt.ingress.class=nginx Your own certificates:\nCreate a Kubernetes secret with tls.crt and tls.key, then:\nhelm upgrade --install rancher rancher-latest/rancher \\ --namespace cattle-system \\ --set hostname=rancher.my.org \\ --set bootstrapPassword=\u0026#39;\u0026lt;CHANGE_ME\u0026gt;\u0026#39; \\ --set ingress.tls.source=secret 5. Verify # kubectl -n cattle-system rollout status deploy/rancher Expect 5–15 minutes for the first install. It is slow because:\nImage pulls: Rancher and cert-manager pull several large images; first pull on a node is the main cost. Certificate issuance: With Rancher-generated certs, cert-manager must create and issue the TLS certificate (often 1–2 minutes). Bootstrap: On first start Rancher initializes its database, runs migrations, installs CRDs, and deploys internal components (e.g. Fleet); only then does the deployment become Ready. To watch progress: kubectl get pods -n cattle-system -w and kubectl get certificate -n cattle-system.\nThen go to the URL to log in and register clusters.\n","externalUrl":null,"permalink":"/docs/rancher/","section":"Documentation","summary":"This guide installs and operates Rancher on an existing Kubernetes cluster using Helm and cert-manager.\nOfficial docs: Install/Upgrade Rancher on a Kubernetes Cluster\nPrerequisites # Kubernetes cluster (K3s, RKE2, EKS, or any supported distribution) kubectl and Helm installed Installation # 1. Add Helm repos # helm repo add rancher-stable https://releases.rancher.com/server-charts/stable helm repo update 2. Create namespace # kubectl create namespace cattle-system 3. Install cert-manager # Required for Rancher-generated TLS certificates (default). Skip if you use your own certificates (ingress.tls.source=secret) or TLS termination on an external load balancer.\n","tags":"","title":"Rancher","type":"docs"},{"content":"Use commands below to install Helm, the package manager for Kubernetes.\ncurl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 chmod 700 get_helm.sh ./get_helm.sh helm version ","externalUrl":null,"permalink":"/docs/helm/","section":"Documentation","summary":"Use commands below to install Helm, the package manager for Kubernetes.\ncurl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 chmod 700 get_helm.sh ./get_helm.sh helm version","tags":"","title":"Helm","type":"docs"},{"content":"","externalUrl":null,"permalink":"/docs/infrastructure/","section":"Documentation","summary":"","tags":"","title":"Infrastructure","type":"docs"},{"content":" Configure # Update the web password before deploy (private repo):\ncp apps/pihole/secret.yaml.template overlays/homelab/pihole/secret.yaml kubectl -n pihole create secret generic pihole-password --from-literal=WEBPASSWORD=\u0026#39;your-strong-password\u0026#39; --dry-run=client -o yaml \u0026gt; overlays/homelab/pihole/secret.yaml Deploy # kubectl apply -k overlays/sample/pihole Private repo:\nkubectl apply -k overlays/homelab/pihole Deco DNS settings # Set the primary DNS in the Deco app to the Pi-hole LoadBalancer IP:\nkubectl -n pihole get svc pihole -o jsonpath=\u0026#34;{.status.loadBalancer.ingress[0].ip}\u0026#34; Argo CD application # Apply the Argo CD stack to register Pi-hole in Argo CD (private repo):\nkubectl apply -k argocd Cloudflare tunnel access # Create a Public Hostname in your existing Cloudflare Tunnel:\nSubdomain: pihole Domain: your-domain Type: HTTP URL: pihole.pihole.svc.cluster.local:80 If you set the origin to HTTPS on port 80, Cloudflare will attempt a TLS handshake against a plain HTTP endpoint and fail with a TLS error. Use HTTP on port 80, or HTTPS on port 443 with No TLS Verify enabled.\nPi-hole serves the UI under /admin, so open:\nhttps://pihole.your-domain/admin/ ","externalUrl":null,"permalink":"/docs/pi-hole/","section":"Documentation","summary":"Configure # Update the web password before deploy (private repo):\ncp apps/pihole/secret.yaml.template overlays/homelab/pihole/secret.yaml kubectl -n pihole create secret generic pihole-password --from-literal=WEBPASSWORD='your-strong-password' --dry-run=client -o yaml \u003e overlays/homelab/pihole/secret.yaml Deploy # kubectl apply -k overlays/sample/pihole Private repo:\nkubectl apply -k overlays/homelab/pihole Deco DNS settings # Set the primary DNS in the Deco app to the Pi-hole LoadBalancer IP:\n","tags":"","title":"Pi-hole","type":"docs"},{"content":"HashiCorp Vault can hold API keys, database passwords, tokens, and other sensitive values so they never appear in Git or Helm values. A typical Kubernetes pattern is: Vault (KV v2) → External Secrets Operator → Kubernetes Secret → Pods (env or volumes). Any workload that reads from a Secret can use this flow; you only align Vault paths and ExternalSecret fields with your app’s expectations.\nRole in the stack # flowchart LR subgraph git[\"Git\"] VY[Helm values / manifests] NS[Namespace and wiring] end subgraph cluster[\"Cluster\"] V[Vault KV v2] ESO[External Secrets Operator] KS[Kubernetes Secret] end VY --\u003e V NS --\u003e V ESO --\u003e|Vault API + token| V ESO --\u003e KS Vault must be running and unsealed before ClusterSecretStore and ExternalSecret resources can sync. In GitOps setups, install Vault first, then External Secrets Operator, then applications that declare ExternalSecret objects (or use equivalent sync waves / dependencies).\nWhat you store in Vault # Use the KV secrets engine (this cluster uses v2 under a mount such as secret). Each secret is a set of key–value fields at a logical path. Convention helps operations:\nPrefix by scope: e.g. team/product, environment/app, or project/service so paths stay unique and policies can match prefixes. Stable field names: use the same property names you will reference in ExternalSecret remoteRef.property (for example DATABASE_URL, API_KEY). One path per logical secret (per app, per environment, or per credential bundle), depending on how you want to rotate and audit. Example (paths and keys are illustrative):\nvault kv put secret/prod/checkout-service DATABASE_URL=\u0026#39;…\u0026#39; API_KEY=\u0026#39;…\u0026#39; Anything that needs to read these values through ESO must be allowed by Vault policy on that path (see below).\nDeploying Vault on Kubernetes # Install Vault with Helm or your GitOps tool. Common choices:\nStandalone with a persistent volume for production-like data retention. TLS: internal-only clusters often use plain HTTP to the Vault Service; use TLS if the API is exposed or policy requires it. Injector: optional; if you only sync via External Secrets Operator, you can disable the sidecar injector. UI: optional, for operators. Pin chart and Vault versions to match your Kubernetes version (kubeVersion in the Helm chart).\nBootstrap after install # Initialize and unseal Vault (HashiCorp quick start). Protect unseal keys and root token.\nEnable KV v2 on the mount your ClusterSecretStore will use (for example secret at path: secret, version: v2). The mount path must match what External Secrets Operator expects in its Vault provider config.\nWrite secrets at paths your applications will reference from ExternalSecret resources:\nvault kv put secret/\u0026lt;env\u0026gt;/\u0026lt;app-name\u0026gt; \u0026lt;KEY\u0026gt;=\u0026lt;value\u0026gt; [\u0026lt;KEY2\u0026gt;=\u0026lt;value2\u0026gt; …] Policies: define a policy that grants read (and list if needed) on exactly the KV paths ESO should read. Attach that policy to a token or identity used by ESO (see the External Secrets Operator page for storing the token in Kubernetes).\nLeast privilege: prefer narrow policies per environment or service family; avoid a single token that can read the entire KV tree unless operational needs justify it.\nOperations # kubectl -n \u0026lt;vault-namespace\u0026gt; get pods # Confirm unsealed; spot-check: vault kv get secret/\u0026lt;path\u0026gt; If Vault is sealed, External Secrets Operator cannot sync; store conditions will show connection or permission errors until Vault is unsealed.\nAdding a new application # Choose a Vault path and field names (see naming above). Write the secret in Vault and extend the ESO token’s policy if the path was not covered before. In the app’s namespace, add an ExternalSecret that maps those paths and properties to the Kubernetes Secret keys your Deployment or Helm chart expects (External Secrets Operator). No change to Vault’s Helm values is required for each new app unless you add new mounts or auth methods.\nRelated # External Secrets Operator — ClusterSecretStore, ExternalSecret, vault-eso-token, troubleshooting. References # HashiCorp Vault documentation KV secrets engine ","externalUrl":null,"permalink":"/docs/hashicorp-vault/","section":"Documentation","summary":"HashiCorp Vault can hold API keys, database passwords, tokens, and other sensitive values so they never appear in Git or Helm values. A typical Kubernetes pattern is: Vault (KV v2) → External Secrets Operator → Kubernetes Secret → Pods (env or volumes). Any workload that reads from a Secret can use this flow; you only align Vault paths and ExternalSecret fields with your app’s expectations.\nRole in the stack # flowchart LR subgraph git[\"Git\"] VY[Helm values / manifests] NS[Namespace and wiring] end subgraph cluster[\"Cluster\"] V[Vault KV v2] ESO[External Secrets Operator] KS[Kubernetes Secret] end VY --\u003e V NS --\u003e V ESO --\u003e|Vault API + token| V ESO --\u003e KS Vault must be running and unsealed before ClusterSecretStore and ExternalSecret resources can sync. In GitOps setups, install Vault first, then External Secrets Operator, then applications that declare ExternalSecret objects (or use equivalent sync waves / dependencies).\n","tags":"","title":"HashiCorp Vault","type":"docs"},{"content":"External Secrets Operator (ESO) watches ExternalSecret resources and materializes ordinary Kubernetes Secret objects from an upstream backend. With HashiCorp Vault as the backend, the same pattern works for any application: you store values in Vault, declare mappings in Git, and mount or env-inject the resulting Secret as usual.\nRole in the stack # flowchart LR subgraph git[\"Git\"] CSS[ClusterSecretStore] X[ExternalSecret per app] end subgraph cluster[\"Cluster\"] ESO[External Secrets Operator] V[Vault KV v2] KS[Kubernetes Secret] P[Pods] end CSS --\u003e ESO X --\u003e ESO ESO --\u003e|token auth| V ESO --\u003e|reconcile| KS KS --\u003e P ClusterSecretStore (cluster-scoped): one connection from the cluster to Vault (URL, mount, auth). Every namespace can reference it. ExternalSecret (namespace-scoped): one per app (or per secret bundle), mapping Vault paths and properties to keys on a target Secret. ESO reconciles on an interval and when specs change. Install ESO after Vault is available and unsealable; install before workloads that depend on synced secrets, or ensure those workloads tolerate a short delay until the Secret exists.\nDeploying the operator # Install from the upstream Helm chart (or your GitOps pipeline). Typical options:\ninstallCRDs: true so ExternalSecret and ClusterSecretStore APIs exist. Replicas: one is enough for small clusters; increase for availability if needed. Version-pin the chart against your Kubernetes version.\nClusterSecretStore: point ESO at Vault # Define a single cluster-wide store (name is arbitrary but must match ExternalSecret references). It must match how Vault is deployed:\nserver: Vault API URL reachable from the ESO pods (often in-cluster Service DNS). path and version: KV mount and v2 if you use KV v2. auth: often a token read from a Kubernetes Secret (long-lived token with a tight Vault policy). Other auth methods (Kubernetes auth, AppRole, etc.) are supported by ESO; token auth is a common starting point. Illustrative structure:\nspec: provider: vault: server: http://\u0026lt;vault-service\u0026gt;.\u0026lt;vault-namespace\u0026gt;.svc.cluster.local:8200 path: secret version: v2 auth: tokenSecretRef: name: \u0026lt;token-secret-name\u0026gt; namespace: \u0026lt;eso-namespace\u0026gt; key: token Adjust server, mount path, and secret reference to your environment.\nBootstrap: Vault token for ESO # Create a Kubernetes Secret in the ESO namespace holding the Vault token ESO should use:\nkubectl create secret generic \u0026lt;token-secret-name\u0026gt; \\ -n \u0026lt;eso-namespace\u0026gt; \\ --from-literal=token=YOUR_VAULT_TOKEN The token must be allowed by Vault policy to read every KV path that any ExternalSecret in the cluster will use (or use multiple stores / auth methods per environment if you split access). After the token exists and Vault is unsealed, the ClusterSecretStore should become Ready.\nExternalSecret: per-application pattern # For each application (or each distinct secret bundle), add an ExternalSecret in the same namespace as the workload:\nField Purpose secretStoreRef kind: ClusterSecretStore, name: \u0026lt;your-store-name\u0026gt; target.name Kubernetes Secret name to create or update data[] / dataFrom Map Vault remoteRef (path + property) to secretKey on the Secret refreshInterval How often to re-sync from Vault (rotation pickup) creationPolicy Often Owner so the Secret is owned by the ExternalSecret resource Path alignment: remoteRef.key is the KV path (for KV v2, the path segment after the mount, e.g. prod/myapp). remoteRef.property is the field name inside that secret. They must match what you wrote with vault kv put.\nWorkload wiring: reference the generated Secret in your Deployment, StatefulSet, or Helm chart (envFrom, env, volumes) the same way you would for any hand-created secret.\nMultiple applications # One ClusterSecretStore can back many ExternalSecret resources in different namespaces. Each app team only needs RBAC to create ExternalSecret in their namespace; they do not need direct Vault access if operations manage Vault paths and ESO token policy. Tighten Vault policy so the ESO token can only read paths that are actually referenced in Git (or split stores/tokens per environment). Operations # kubectl get clustersecretstore kubectl get externalsecret -A kubectl describe externalsecret -n \u0026lt;namespace\u0026gt; \u0026lt;name\u0026gt; Rotate the Vault token used by ESO # Issue a new token (or update policy + token) in Vault. Update the Kubernetes token Secret ESO uses (same name and key as in tokenSecretRef). ESO reconciles automatically; restart ESO pods if you need an immediate reconnect. Common issues # Symptom Things to verify ClusterSecretStore not ready Vault reachable and unsealed; token secret present and valid; network from ESO namespace to Vault. ExternalSecret not synced remoteRef path and property match KV data; Vault policy allows read; target namespace exists. Generated Secret empty or stale refreshInterval; verify with vault kv get on the same path and property. Related # HashiCorp Vault — KV layout, policies, unseal, adding paths for new apps. References # External Secrets Operator Vault provider ","externalUrl":null,"permalink":"/docs/external-secrets-operator/","section":"Documentation","summary":"External Secrets Operator (ESO) watches ExternalSecret resources and materializes ordinary Kubernetes Secret objects from an upstream backend. With HashiCorp Vault as the backend, the same pattern works for any application: you store values in Vault, declare mappings in Git, and mount or env-inject the resulting Secret as usual.\nRole in the stack # flowchart LR subgraph git[\"Git\"] CSS[ClusterSecretStore] X[ExternalSecret per app] end subgraph cluster[\"Cluster\"] ESO[External Secrets Operator] V[Vault KV v2] KS[Kubernetes Secret] P[Pods] end CSS --\u003e ESO X --\u003e ESO ESO --\u003e|token auth| V ESO --\u003e|reconcile| KS KS --\u003e P ClusterSecretStore (cluster-scoped): one connection from the cluster to Vault (URL, mount, auth). Every namespace can reference it. ExternalSecret (namespace-scoped): one per app (or per secret bundle), mapping Vault paths and properties to keys on a target Secret. ESO reconciles on an interval and when specs change. Install ESO after Vault is available and unsealable; install before workloads that depend on synced secrets, or ensure those workloads tolerate a short delay until the Secret exists.\n","tags":"","title":"External Secrets Operator","type":"docs"},{"content":"Download the image from https://cloud-images.ubuntu.com/ (tested on https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img)\nCreate and configure a new VM via CLI on the host\nqm create 9999 --memory 8192 --core 2 --name ubuntu-cloud --net0 virtio,bridge=vmbr0 cd /var/lib/vz/template/iso/ # or to the location where the image was downloaded qm importdisk 9999 \u0026lt;IMAGE NAME\u0026gt; \u0026lt;YOUR STORAGE HERE\u0026gt; qm set 9999 --scsihw virtio-scsi-pci --scsi0 \u0026lt;YOUR STORAGE HERE\u0026gt;:9999/vm-9999-disk-0.raw qm set 9999 --ide2 \u0026lt;YOUR STORAGE HERE\u0026gt;:cloudinit qm set 9999 --boot c --bootdisk scsi0 qm set 9999 --serial0 socket --vga serial0 qm disk resize 9999 scsi0 64G Go to the UI, and set User, Password, SSH public key, IP Config = DHCP\nConvert the VM to template\nDeploy new VMs by cloning the template (full clone) or using Terraform\n","externalUrl":null,"permalink":"/docs/cloud-init/","section":"Documentation","summary":"Download the image from https://cloud-images.ubuntu.com/ (tested on https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img)\nCreate and configure a new VM via CLI on the host\nqm create 9999 --memory 8192 --core 2 --name ubuntu-cloud --net0 virtio,bridge=vmbr0 cd /var/lib/vz/template/iso/ # or to the location where the image was downloaded qm importdisk 9999 \u003cIMAGE NAME\u003e \u003cYOUR STORAGE HERE\u003e qm set 9999 --scsihw virtio-scsi-pci --scsi0 \u003cYOUR STORAGE HERE\u003e:9999/vm-9999-disk-0.raw qm set 9999 --ide2 \u003cYOUR STORAGE HERE\u003e:cloudinit qm set 9999 --boot c --bootdisk scsi0 qm set 9999 --serial0 socket --vga serial0 qm disk resize 9999 scsi0 64G Go to the UI, and set User, Password, SSH public key, IP Config = DHCP\n","tags":"","title":"Cloud-init","type":"docs"},{"content":"","externalUrl":null,"permalink":"/docs/platform/","section":"Documentation","summary":"","tags":"","title":"Platform","type":"docs"},{"content":"","externalUrl":null,"permalink":"/docs/applications/","section":"Documentation","summary":"","tags":"","title":"Applications","type":"docs"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/blog/","section":"","summary":"","tags":"","title":"","type":"blog"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/capa/","section":"Tags","summary":"","tags":"","title":"Capa","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/cba/","section":"Tags","summary":"","tags":"","title":"Cba","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/cca/","section":"Tags","summary":"","tags":"","title":"Cca","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/certification/","section":"Tags","summary":"","tags":"","title":"Certification","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/cgoa/","section":"Tags","summary":"","tags":"","title":"Cgoa","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/cka/","section":"Tags","summary":"","tags":"","title":"Cka","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/ckad/","section":"Tags","summary":"","tags":"","title":"Ckad","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/cks/","section":"Tags","summary":"","tags":"","title":"Cks","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/cncf/","section":"Tags","summary":"","tags":"","title":"Cncf","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/cnpa/","section":"Tags","summary":"","tags":"","title":"Cnpa","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/cnpe/","section":"Tags","summary":"","tags":"","title":"Cnpe","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/golden-kubestronaut/","section":"Tags","summary":"","tags":"","title":"Golden-Kubestronaut","type":"tags"},{"content":" About # I write about cloud-native technologies, platform engineering, and self-hosting at mi-homes.org. Practical insights from running production systems.\n","date":"March 22, 2026","externalUrl":null,"permalink":"/","section":"Home","summary":" About # I write about cloud-native technologies, platform engineering, and self-hosting at mi-homes.org. Practical insights from running production systems.\n","tags":"","title":"Home","type":"page"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/homelab/","section":"Tags","summary":"","tags":"","title":"Homelab","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/ica/","section":"Tags","summary":"","tags":"","title":"Ica","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/kca/","section":"Tags","summary":"","tags":"","title":"Kca","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/kcna/","section":"Tags","summary":"","tags":"","title":"Kcna","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/kcsa/","section":"Tags","summary":"","tags":"","title":"Kcsa","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/kubecon/","section":"Tags","summary":"","tags":"","title":"Kubecon","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/kubernetes/","section":"Tags","summary":"","tags":"","title":"Kubernetes","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/kubestronaut/","section":"Tags","summary":"","tags":"","title":"Kubestronaut","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/lfcs/","section":"Tags","summary":"","tags":"","title":"Lfcs","type":"tags"},{"content":" I\u0026rsquo;m aiming for Golden Kubestronaut next year. # I just like having a hard, clear target to chase in this time period. It is one of those challenges that forces me to tighten the basics, notice the gaps I may sometimes work around, and then zoom out into the rest of the CNCF ecosystem.\nIf I only learn what the current sprint needs, I get really good at a narrow slice of the stack. If I only follow whatever catches my attention, I end up with a pile of half-finished experiments. This plan is my attempt to get the best of both worlds: stay sharp for work, and still keep exploring in a structured way.\nAnd yes, the \u0026ldquo;Golden\u0026rdquo; part is tempting too. Passing every CNCF certification means you get the Golden Kubestronaut title for life, joining a group achieving the highest level of CNCF certification, plus additional benefits such as free KCD tickets, discounted KubeCon registrations, and ongoing discounts for future professional trainings.\nMy current status and timeline # The core \u0026ldquo;Kubestronaut\u0026rdquo; path has 5 exams:\nCKAD (Certified Kubernetes Application Developer) CKA (Certified Kubernetes Administrator) CKS (Certified Kubernetes Security Specialist) KCNA (Kubernetes and Cloud Native Associate) KCSA (Kubernetes and Cloud Native Security Associate) I already have CKAD and CKA.\nNext are KCNA and KCSA that will be done very soon. Before CKS, I am considering LFCS (Linux Foundation Certified System Administrator) first—the extra Linux depth should make the security specialist exam easier. I plan to take CKS this summer, which should complete the core set and get me to Kubestronaut.\nOnce Kubestronaut is done, I\u0026rsquo;ll do the remaining exams and have one more year to finish the full Golden track, intentionally doing \u0026ldquo;core first, breadth second.\u0026rdquo;\nFor clarity, the other exams are:\nLFCS (Linux Foundation Certified System Administrator) - as mentioned, I will do it before CKS CCA (Cilium Certified Associate) ICA (Istio Certified Associate) KCA (Kyverno Certified Associate) CBA (Certified Backstage Associate) CGOA (Certified GitOps Associate) CAPA (Certified Argo Project Associate) PCA (Prometheus Certified Associate) OTCA (OpenTelemetry Certified Associate) CNPA (Certified Cloud Native Platform Engineering Associate) CNPE (Certified Cloud Native Platform Engineer) How this fits my work and life # Kubernetes is becoming central to my work, which makes this plan realistic. Work gives me the real constraints: clusters with trade-offs, competing priorities, and the kind of problems only met in production. In the evenings, I use the study time to turn those experiences into deliberate reps, and to train this: being fast and correct under time pressure.\nI\u0026rsquo;ll prepare only in the evenings and keep it simple: visit some key topics, run hands-on labs, and explain concepts in my own words. To avoid spreading myself too thin, I will stick with the materials that already worked well for me: KodeKloud, Killer Shell, and Cloudastic.\nMy home cluster is where I go wide # I use it to try tools, connect them, break things safely, fix them, and document the sharp edges while they\u0026rsquo;re still fresh. While building a production-grade home cluster, I try to gain as much hands-on experience as possible.\nWhy I will keep blogging # I write these posts first for myself. Sometimes they are architecture notes or implementation details. Sometimes they are just deep dives into a specific topic I ran into.\nNext to the blog posts, I am also building a docs section. Over time it will become the detailed, step-by-step reference for things I have tried and know work, so I do not have to rediscover the same solutions later.\nIf it also helps someone else who finds my website, that is a nice bonus.\nCost plan # These exams are expensive, so I\u0026rsquo;ll buy them intentionally. I already bought the first half of the bundle. For the second half, attending KubeCon Europe 2026 in Amsterdam gives me quite a decent discount on the purchase.\nI am joining KubeCon Europe 2026 in Amsterdam # If you\u0026rsquo;re attending, I\u0026rsquo;d love to connect—say hi, and let\u0026rsquo;s share experiences, production tips, and lessons learned in person.\n","date":"March 22, 2026","externalUrl":null,"permalink":"/blog/008-golden-kubestronaut-plan/","section":"","summary":" I’m aiming for Golden Kubestronaut next year. # I just like having a hard, clear target to chase in this time period. It is one of those challenges that forces me to tighten the basics, notice the gaps I may sometimes work around, and then zoom out into the rest of the CNCF ecosystem.\n","tags":"kubernetes cncf the-linux-foundation kubestronaut golden-kubestronaut certification ckad cka cks kcna kcsa pca ica cca capa cgoa cba otca kca cnpa cnpe lfcs platform-engineering homelab kubecon","title":"My Plan to Become a Golden Kubestronaut Next Year","type":"blog"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/otca/","section":"Tags","summary":"","tags":"","title":"Otca","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/pca/","section":"Tags","summary":"","tags":"","title":"Pca","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/platform-engineering/","section":"Tags","summary":"","tags":"","title":"Platform-Engineering","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","tags":"","title":"Tags","type":"tags"},{"content":"","date":"March 22, 2026","externalUrl":null,"permalink":"/tags/the-linux-foundation/","section":"Tags","summary":"","tags":"","title":"The-Linux-Foundation","type":"tags"},{"content":"","date":"January 31, 2026","externalUrl":null,"permalink":"/tags/ai/","section":"Tags","summary":"","tags":"","title":"Ai","type":"tags"},{"content":"","date":"January 31, 2026","externalUrl":null,"permalink":"/tags/ai-infrastructure/","section":"Tags","summary":"","tags":"","title":"Ai-Infrastructure","type":"tags"},{"content":"","date":"January 31, 2026","externalUrl":null,"permalink":"/tags/apple-silicon/","section":"Tags","summary":"","tags":"","title":"Apple-Silicon","type":"tags"},{"content":"","date":"January 31, 2026","externalUrl":null,"permalink":"/tags/gpu/","section":"Tags","summary":"","tags":"","title":"Gpu","type":"tags"},{"content":"","date":"January 31, 2026","externalUrl":null,"permalink":"/tags/llm/","section":"Tags","summary":"","tags":"","title":"Llm","type":"tags"},{"content":"","date":"January 31, 2026","externalUrl":null,"permalink":"/tags/machine-learning/","section":"Tags","summary":"","tags":"","title":"Machine-Learning","type":"tags"},{"content":" What is Modular and Mojo?\nIf you\u0026rsquo;re building AI systems, you\u0026rsquo;ve probably felt the pain of juggling multiple frameworks and tools. Modular is trying to solve that problem by creating a unified platform for high-performance AI development.\nModular\u0026rsquo;s MAX framework is their answer to the messy world of AI inference. It\u0026rsquo;s designed to let you write model code once and run it efficiently on different hardware—whether that\u0026rsquo;s NVIDIA GPUs, AMD cards, or Apple Silicon. Think of it as a bridge between the flexibility you need during development and the performance you need in production. The framework handles optimization and hardware-specific compilation so you don\u0026rsquo;t have to maintain separate codebases for each platform.\nMojo is their systems programming language built specifically for AI workloads. If you\u0026rsquo;ve ever wished Python had the raw speed of C++ while keeping its approachable syntax, that\u0026rsquo;s what Mojo aims to deliver. It\u0026rsquo;s especially useful for writing GPU kernels and low-level infrastructure code where performance matters. The language includes modern safety features borrowed from languages like Rust, helping you catch bugs at compile time rather than in production.\nWhy does this matter? Most AI teams today prototype in PyTorch, then scramble to rewrite everything for production using completely different tools. Modular wants to eliminate that gap.\nWhat\u0026rsquo;s New in Version 26.1 # The latest release on January 29th focuses here is on making the developer experience feel less like fighting with infrastructure and more like actually building AI systems.\nProduction-Ready Python APIs # The headline feature is that MAX\u0026rsquo;s Python APIs have moved out of experimental status. What does that mean practically? You now have a development workflow that feels similar to PyTorch but with a clear path to production optimization.\nDuring development, you work in eager execution mode—code runs immediately, debugging is straightforward, and the iteration loop is fast. When you\u0026rsquo;re ready to deploy, calling model.compile() transforms your model into an optimized graph that takes advantage of MAX\u0026rsquo;s compiler and hardware-specific kernels. No rewrite required, just a different execution strategy.\nThis closes what\u0026rsquo;s historically been a frustrating gap: the tools that make development pleasant (like PyTorch\u0026rsquo;s eager mode) are different from the tools that make deployment fast (like TensorRT). MAX is betting you shouldn\u0026rsquo;t have to choose.\nLearning Materials Have Matured # Their LLM tutorial—the MAX LLM Book—is now production-ready and actively maintained. It\u0026rsquo;s a step-by-step guide that takes you through building a transformer model from the ground up. You start with basic concepts like tokenization and work your way through attention mechanisms until you have a working model that\u0026rsquo;s compatible with OpenAI\u0026rsquo;s API.\nThe tutorial includes runnable code at each stage, which makes it useful both as a learning tool and as a reference when you need to implement custom architectures. If you\u0026rsquo;ve ever wanted to really understand transformer internals beyond the high-level explanations, this is a solid resource.\nMac GPUs Are Getting First-Class Support # Apple Silicon GPU support has improved significantly in this release. Simple MAX computation graphs now compile and run on Mac GPUs, and most of the Mojo GPU examples work (except those specifically written for NVIDIA hardware).\nThey\u0026rsquo;re planning to extend this all the way to full LLM inference on Mac GPUs in future releases. For developers working on Apple hardware, this means you\u0026rsquo;ll eventually be able to do real AI work locally without needing remote GPU instances.\nMojo Gets More Sophisticated # On the language side, Mojo is picking up features that move it closer to a 1.0 release. The additions in 26.1 focus on compile-time safety and developer ergonomics:\nReflection at compile time lets you write code that inspects types and generates implementations automatically. Think automatic serialization to JSON, derived equality checks, or CLI argument parsing—the kind of boilerplate that\u0026rsquo;s tedious to write manually.\nLinear types are a feature from programming language research that ensures certain values must be explicitly destroyed. This catches resource leaks at compile time, giving you stronger guarantees than most mainstream languages provide.\nTyped exceptions mean error handling now works on GPUs and embedded systems. Previously, exception overhead made them impractical in these environments.\nThe error messages have also gotten better. Instead of cryptic \u0026ldquo;can\u0026rsquo;t infer parameter\u0026rdquo; messages, the compiler now shows you where types differ and highlights the specific mismatch. Small quality-of-life improvements like this matter when you\u0026rsquo;re debugging complex code.\nWhy This Release Matters # This release is significant because it shows the platform is maturing beyond early-stage experimentation. The Python APIs graduating from experimental status signals that Modular is confident enough in the stability for production use.\nThe real value proposition is about reducing infrastructure complexity. Instead of maintaining separate codebases for development (PyTorch), serving (TensorRT or similar), and hardware-specific optimizations, you work in one framework. That\u0026rsquo;s appealing for small teams that don\u0026rsquo;t have dedicated infrastructure engineers.\nFor multi-platform deployments, the portability story is compelling. The costs can differ between NVIDIA, AMD, and emerging hardware. Being able to switch without rewriting your model code gives you leverage in negotiating cloud contracts and flexibility to chase cost savings.\nThe community momentum suggests the platform has staying power. When external developers start building real infrastructure on your platform, that\u0026rsquo;s usually a good indicator.\nGetting Your Hands On It # Installation is straightforward through standard Python package managers:\nuv pip install modular Once installed, you can dive into the LLM tutorial to build transformers from scratch, experiment with GPU programming using Mojo puzzles, or explore the new language features like reflection and linear types.\nThe original announcement has full changelogs and technical details if you want to dig deeper.\nFinal Thoughts # The AI infrastructure landscape is notoriously fragmented. If Modular can deliver on their promise of unified tooling from research to production, they\u0026rsquo;ll solve a real problem. This release suggests they\u0026rsquo;re making tangible progress toward that vision.\nWhether the platform gains broad adoption depends on factors beyond just technical merit—ecosystem effects, corporate backing, and developer mindshare all matter. But for teams frustrated with current AI infrastructure complexity, it\u0026rsquo;s worth evaluating. The fact that it\u0026rsquo;s largely open source reduces lock-in risk, which helps with the adoption decision.\n","date":"January 31, 2026","externalUrl":null,"permalink":"/blog/005-high-performance-ai-development/","section":"","summary":" What is Modular and Mojo?\nIf you’re building AI systems, you’ve probably felt the pain of juggling multiple frameworks and tools. Modular is trying to solve that problem by creating a unified platform for high-performance AI development.\nModular’s MAX framework is their answer to the messy world of AI inference. It’s designed to let you write model code once and run it efficiently on different hardware—whether that’s NVIDIA GPUs, AMD cards, or Apple Silicon. Think of it as a bridge between the flexibility you need during development and the performance you need in production. The framework handles optimization and hardware-specific compilation so you don’t have to maintain separate codebases for each platform.\n","tags":"modular mojo ai machine-learning python gpu apple-silicon ai-infrastructure llm performance open-source mlops programming-languages","title":"Making High-Performance AI Development More Accessible","type":"blog"},{"content":"","date":"January 31, 2026","externalUrl":null,"permalink":"/tags/mlops/","section":"Tags","summary":"","tags":"","title":"Mlops","type":"tags"},{"content":"","date":"January 31, 2026","externalUrl":null,"permalink":"/tags/modular/","section":"Tags","summary":"","tags":"","title":"Modular","type":"tags"},{"content":"","date":"January 31, 2026","externalUrl":null,"permalink":"/tags/mojo/","section":"Tags","summary":"","tags":"","title":"Mojo","type":"tags"},{"content":"","date":"January 31, 2026","externalUrl":null,"permalink":"/tags/open-source/","section":"Tags","summary":"","tags":"","title":"Open-Source","type":"tags"},{"content":"","date":"January 31, 2026","externalUrl":null,"permalink":"/tags/performance/","section":"Tags","summary":"","tags":"","title":"Performance","type":"tags"},{"content":"","date":"January 31, 2026","externalUrl":null,"permalink":"/tags/programming-languages/","section":"Tags","summary":"","tags":"","title":"Programming-Languages","type":"tags"},{"content":"","date":"January 31, 2026","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","tags":"","title":"Python","type":"tags"},{"content":"How to build a public Kubernetes GitOps repo that others can reuse — without leaking secrets or creating a maintenance nightmare.\nThe Problem You May Hit # You\u0026rsquo;ve been running GitOps and Kubernetes in your homelab for a while. Things are working well. You want to share your configuration publicly so others can replicate it. But you also need to keep your actual cluster configuration private—real hostnames, IP addresses, secrets, all the stuff you definitely don\u0026rsquo;t want on a published Git repository.\nAt this point, you may ask the question:\nHow do I sync my public GitOps repo into my private one?\nActually, the correct solution isn\u0026rsquo;t syncing repositories. It\u0026rsquo;s consuming multiple repositories directly in a GitOps operator. Let me show you what I mean.\nWhy GitOps Matters (A Quick Refresher) # Before we get into the pattern that works, let\u0026rsquo;s make sure we\u0026rsquo;re aligned on what GitOps actually is and why it matters.\nGitOps uses Git as the single source of truth for your infrastructure. Every change to your cluster happens through a Git commit. An operator running in your cluster—like Argo CD or Flux—continuously watches your Git repositories and automatically reconciles the cluster state with what Git declares.\nIn traditional approaches, you push changes to the cluster from outside. You run kubectl apply manually, or your CI/CD pipeline executes deployment commands. The deployment is event-driven. Between deployments, your cluster can drift, and you won\u0026rsquo;t know unless you apply and check.\nWith GitOps, all configuration lives in Git. You make changes through pull requests and code reviews. The operator running in your cluster continuously pulls changes every short interval and ensures the cluster state matches Git. No one directly modifies the cluster with kubectl apply. The cluster state always converges toward what Git declares.\nWhy This Is a Good Idea # Version control for everything: Every change has a commit history. You can see who changed what, when, and why. Rolling back is as simple as reverting a commit.\nGit is the single source of truth: If your cluster is destroyed, you can recreate it entirely from Git.\nAutomated reconciliation: The GitOps operator continuously ensures the cluster matches Git. Manual drift gets automatically corrected. Configuration drift becomes impossible.\nBetter security: Cluster credentials don\u0026rsquo;t need to be shared with CI/CD pipelines or developers. The operator pulls changes from Git rather than external systems pushing changes to the cluster.\nImproved collaboration: Infrastructure changes go through the same review process as code. Pull requests enable discussion and approval workflows.\nDisaster recovery: Your entire cluster configuration is in Git. Recovery from catastrophic failure is reproducible and auditable.\nOnce you experience the confidence that comes from knowing your entire cluster state is version-controlled and continuously reconciled, you wouldn\u0026rsquo;t want to go back.\nThe Core Principle: Repositories Have Different Purposes # Here\u0026rsquo;s the mental model that works for me:\nRepository Responsibility Public repo Reusable Kubernetes baseline Private repo Cluster-specific desired state Trying to \u0026ldquo;sync\u0026rdquo; them leads to problems—accidental secret leakage, merge conflicts, drift between repositories, and unclear ownership of configuration.\nInstead, treat your public repository like upstream software. Think about how you use Helm charts: the chart defines what can be deployed, and your values.yaml file defines how it\u0026rsquo;s deployed in your specific environment.\nPublic repo = dependency\nPrivate repo = configuration\nThis is exactly like Helm charts and values.yaml, just applied to your entire infrastructure.\nRepository 1: Public (Reusable Baseline) # My public repository contains no secrets, no IP addresses, no real hostnames—nothing cluster-specific at all.\nInstead, it defines what to deploy. Base configurations. Default values. Generic Helm charts. Kustomize bases. Everything structured so that someone else could take it and adapt it to their own infrastructure.\nRepository 2: Private (Cluster Overlay) # My private repository contains all my Argo CD Applications, environment-specific overrides, and values that are specific to my actual cluster.\nThis is where I define how and where things run in my infrastructure.\nHere\u0026rsquo;s the key insight: Argo CD consumes both repositories directly. No syncing. No copying files. No git submodules or clever automation. Argo CD just reads from both repos at the same time.\nWhat Goes in the Public Repo # The public repository needs to be something someone else can actually use. Here is an example structure:\nhomelabs/ ├── apps/ │ ├── app-01/ │ | ├── deployment.yaml │ | ├── kustomization.yaml │ | ├── pvc.yaml │ | ├── secret.yaml.template │ | └── service.yaml │ └── app-02/ What Belongs Here # Generic stuff that makes sense on any cluster: Helm charts, Kustomize bases, default values, placeholder domains. These are reusable building blocks.\nWhat Doesn\u0026rsquo;t Belong Here # Anything specific to my cluster: secrets, IP addresses, real hostnames, storage paths. If it wouldn\u0026rsquo;t make sense on someone else\u0026rsquo;s cluster, it doesn\u0026rsquo;t go in the public repo.\nEvery time I add something to the public repo, I ask: \u0026ldquo;Could someone else use this as-is?\u0026rdquo; If the answer is no, it belongs in the private repo instead.\nWhat Goes in the Private Repo # The private repository represents my actual cluster\u0026rsquo;s desired state, for example:\nhomelabs-private/ ├── argocd/ │ ├── kustomization.yaml │ └── projects/ │ └── homelab.yaml ├── clusters/ │ └── home-prod/ │ ├── argocd/ │ │ ├── app-of-apps.yaml │ │ └── applications/ │ │ ├── app-01.yaml │ │ └── app-02.yaml │ └── overlays/ │ ├── app-01/ │ │ ├── kustomization.yaml │ │ └── secret.yaml │ └── app-02/ │ ├── kustomization.yaml │ └── secret.yaml This is where Argo CD itself lives, where applications are instantiated, where versions are pinned, and where all my cluster-specific configuration exists.\nHow Argo CD Ties Everything Together # Argo CD has a feature called multi-source Applications. This is what makes the entire pattern work.\nHere\u0026rsquo;s an actual example from my setup:\napiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: app-01 namespace: argocd spec: project: homelab sources: - repoURL: https://github.com/example/homelabs.git targetRevision: main path: apps/app-01 - repoURL: https://github.com/example/homelabs-private.git targetRevision: main path: clusters/home-prod/overlays/app-01 destination: server: https://kubernetes.default.svc namespace: app-01 syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true Look at what this achieves:\nThe public repo defines the application (homelabs/apps/app-01) The private repo defines the cluster-specific bits (homelabs-private/clusters/home-prod/overlays/app-01/secret.yaml) There\u0026rsquo;s no copying, no syncing, no risk of secrets leaking When I update the public repo with a new feature, other people can pick it up by bumping a tag (or by tracking main). When I update my private overlays (secrets, overrides), only my cluster changes. Clean separation of concerns.\nHow Others Can Reuse My Public Repository # A new user doesn\u0026rsquo;t need to clone my entire private structure or figure out what\u0026rsquo;s specific to my setup.\nThey just create their own private repository, write their own Argo CD Applications, and reference my public repo as upstream. They pull the base configuration from my public repo and apply their own overrides in their private repo. No complexity. No maintenance burden. Lowest-friction reuse.\nVersioning and Promotion: Why I Use Trunk-Based Development # I used to overthink this. I tried GitFlow with main, develop, feature, release, and hotfix branches. It is not the best for GitOps.\nDon\u0026rsquo;t get me wrong—GitFlow works perfectly well in the right context. At work, I use GitFlow for medical software development where we have rigorous testing and release processes, with strict regulatory requirements. In that environment, the ceremony of GitFlow makes sense. I also use GitOps at work for the platform side; for GitOps—there and here—I want something simpler: determinism, immutability, and clear promotion paths.\nWhat I Actually Use Here # Trunk-based development:\nmain → always releasable feature/* → short-lived tags → v1.0.0, v1.1.0 Public Repo Versioning # The main branch is always stable. Every meaningful change gets a new tag using semantic versioning. No environment branches. No long-lived feature branches.\nPrivate Repo Versioning # The main branch reflects my current cluster state. Promotion happens through version bumps, not branch merges.\nIf I had multiple environments (dev/staging/prod), they\u0026rsquo;d be directories, not branches:\nclusters/ ├── home-dev/ └── home-prod/ How I Promote a New Version # This is the actual workflow I use:\n1. Tag a new version in the public repo:\ncd homelabs/ git tag v1.2.0 git push origin v1.2.0 2. Update the private repo to reference the new version:\ncd homelabs-private/ # Edit the Application manifest vim clusters/home-prod/argocd/applications/app-01.yaml # If you pin the public repo to tags, bump the homelabs source targetRevision: v1.1.0 → v1.2.0 git add clusters/home-prod/argocd/applications/app-01.yaml git commit -m \u0026#34;Promote app-01 to v1.2.0\u0026#34; git push origin main 3. Argo CD automatically syncs the new version to my cluster (or I manually sync if auto-sync is disabled).\nIf I had a dev environment, I\u0026rsquo;d test there first:\n# Update dev environment vim clusters/home-dev/argocd/applications/app-01.yaml # Change to v1.2.0, test it, then promote to prod This workflow is clean, auditable, and requires zero automation beyond Argo CD itself.\nWhy This Works: The Mental Model and Long-Term Benefits # Here\u0026rsquo;s the core insight: Don\u0026rsquo;t sync repositories. Consume them.\nYour public repo isn\u0026rsquo;t \u0026ldquo;your cluster.\u0026rdquo; It\u0026rsquo;s a product that others can consume. Your private repo is your actual cluster configuration. Argo CD is designed to handle this pattern. Multi-source Applications exist specifically for this use case. Once you stop fighting the tool and embrace the pattern, everything becomes simpler.\nI\u0026rsquo;ve been running this setup for a while now, and it scales beautifully. Here\u0026rsquo;s why:\nClear ownership: The public repo owns the baseline. The private repo owns the environment. No confusion about where something belongs.\nNo drift: Argo CD continuously reconciles both sources. What\u0026rsquo;s in Git is what runs in the cluster.\nEasy rollback: Change one line in the private repo to pin an older version. Argo CD handles the rest.\nSafe sharing: I can share my public repo without worrying about accidentally leaking secrets or cluster-specific details.\nReproducible: If I need to rebuild my cluster from scratch, everything I need is in Git.\nThis model is useful in regulated environments where auditability matters. It\u0026rsquo;s used for multi-cluster setups. And it works just as well for a homelab.\n","date":"January 17, 2026","externalUrl":null,"permalink":"/blog/003-public-gitops-repo-without-exposing-cluster-secrets/","section":"","summary":"How to build a public Kubernetes GitOps repo that others can reuse — without leaking secrets or creating a maintenance nightmare.\nThe Problem You May Hit # You’ve been running GitOps and Kubernetes in your homelab for a while. Things are working well. You want to share your configuration publicly so others can replicate it. But you also need to keep your actual cluster configuration private—real hostnames, IP addresses, secrets, all the stuff you definitely don’t want on a published Git repository.\n","tags":"gitops argocd kubernetes homelab devops sre infrastructure-as-code","title":"A Pattern for Sharing Replicable Public Kubernetes GitOps Repos Without Leaking Cluster Secrets","type":"blog"},{"content":"","date":"January 17, 2026","externalUrl":null,"permalink":"/tags/argocd/","section":"Tags","summary":"","tags":"","title":"Argocd","type":"tags"},{"content":"","date":"January 17, 2026","externalUrl":null,"permalink":"/tags/devops/","section":"Tags","summary":"","tags":"","title":"Devops","type":"tags"},{"content":"","date":"January 17, 2026","externalUrl":null,"permalink":"/tags/gitops/","section":"Tags","summary":"","tags":"","title":"Gitops","type":"tags"},{"content":"","date":"January 17, 2026","externalUrl":null,"permalink":"/tags/infrastructure-as-code/","section":"Tags","summary":"","tags":"","title":"Infrastructure-as-Code","type":"tags"},{"content":"","date":"January 17, 2026","externalUrl":null,"permalink":"/tags/sre/","section":"Tags","summary":"","tags":"","title":"Sre","type":"tags"},{"content":"","date":"January 6, 2026","externalUrl":null,"permalink":"/tags/geeekpi/","section":"Tags","summary":"","tags":"","title":"Geeekpi","type":"tags"},{"content":"","date":"January 6, 2026","externalUrl":null,"permalink":"/tags/hardware/","section":"Tags","summary":"","tags":"","title":"Hardware","type":"tags"},{"content":"","date":"January 6, 2026","externalUrl":null,"permalink":"/tags/infrastructure/","section":"Tags","summary":"","tags":"","title":"Infrastructure","type":"tags"},{"content":"","date":"January 6, 2026","externalUrl":null,"permalink":"/tags/k3s/","section":"Tags","summary":"","tags":"","title":"K3s","type":"tags"},{"content":"","date":"January 6, 2026","externalUrl":null,"permalink":"/tags/minisforum/","section":"Tags","summary":"","tags":"","title":"Minisforum","type":"tags"},{"content":"","date":"January 6, 2026","externalUrl":null,"permalink":"/tags/self-hosted/","section":"Tags","summary":"","tags":"","title":"Self-Hosted","type":"tags"},{"content":"","date":"January 6, 2026","externalUrl":null,"permalink":"/tags/server-rack/","section":"Tags","summary":"","tags":"","title":"Server-Rack","type":"tags"},{"content":"","date":"January 6, 2026","externalUrl":null,"permalink":"/tags/synology/","section":"Tags","summary":"","tags":"","title":"Synology","type":"tags"},{"content":"One of the questions I got several times is about what hardware I am actually running.\nYou don\u0026rsquo;t need enterprise servers to run production infrastructure. The compact 8U rack sitting under my desk handles everything I need. This isn\u0026rsquo;t a buyer\u0026rsquo;s guide with exhaustive tier comparisons. Instead, I want to share my actual setup—the mini server rack that transformed my homelab, the mini workstation that became my primary workhorse, and the lessons learned from building infrastructure that people depend on daily.\nThe Rack That Accommodates It All # This compact rack represents the current state of my homelab. It didn\u0026rsquo;t start here.\nMy self-hosting journey began a long time ago with a single custom-built desktop PC running 24/7. It handled almost everything—Plex, Immich, Tailscale, file serving, development environments, external hard disk backup, etc. Then I added a Synology NAS, which turned out to be a very good addition. Network storage with proper backups changed how I thought about data persistence.\nAs workloads grew, I connected old computers lying around my house to form a cluster. The infrastructure expanded organically, but without intentional organization. Equipment spread across shelves and desk space. Cables became tangled. It worked, but it didn\u0026rsquo;t feel like infrastructure I could be proud of.\nThen, a GeeekPi 8U Server Rack transformed everything. Most of the hardware now sits in this rack - a Minisforum MS-01 mini workstation, a 4-bay Synology NAS, a network switch, power splitters, a Hue smart hub, all properly mounted with clean cable management. An old laptop and the desktop are still connected to the cluster for high availability, but they sit outside the rack. Eventually, I\u0026rsquo;ll replace them with additional mini workstations, and the entire infrastructure will be contained in this compact frame. That\u0026rsquo;s the goal: production-quality infrastructure in a form factor that fits in a small space.\nThe 8U height is perfect—tall enough for proper equipment mounting, compact enough without dominating the room. The open-frame design provides natural airflow, so I\u0026rsquo;ve never needed rack fans. Cable management inside forces me to be intentional about organization. When everything has a proper mount point, you think twice before adding random hardware sprawl.\nThe portability is another advantage I didn\u0026rsquo;t fully appreciate until I needed it. I can unplug the entire rack—one power cable, one network cable—and move it to a different location. Plug it back in, and everything works exactly the same, regardless of which internet provider or network I\u0026rsquo;m connecting to. Internally, everything is connected together, so the clean external wiring—just those two cables—belies the full mesh of connectivity inside the rack.\nOne detail I appreciate: the GeeekPi rack is extensible. If I need more space in the future, I can stack additional 4U or 8U units to create a 12U or 16U rack. This modularity means I\u0026rsquo;m not locked into the current capacity. As my infrastructure needs grow—more mini workstations, a UPS, additional networking gear—I can expand vertically without replacing the rack.\nInside the rack, I mounted a DIGITUS 4-way power strip and added GeeekPi DeskPi RackMate shelves to hold the Synology NAS and network switch. For clean cable routing, I added an Ianberg 1U fixed drawer and a DIGITUS cable entry panel. Short Cat6a patch cables (0.5m and 1m) keep everything tidy. A few UGREEN USB-C cables handle peripheral connections.\nThe total rack infrastructure cost about €210, but the operational value is enormous. Physical organization creates mental clarity. When you walk up to a professional-looking rack instead of a pile of equipment on a shelf, you treat it like production infrastructure. This discipline carries over to how you architect and maintain the cluster.\nRack Setup Inspiration # Before I bought my own rack, I spent time looking at other people\u0026rsquo;s setups to understand what worked well. Here are a few examples that inspired my design:\nSources:\nConsolidating mini things in the GeeekPi rack Tiny Homelab Server Rack: Mini but Mighty Jeff Geerling\u0026rsquo;s Mini Rack Project Complete HomeLab - Proxmox, TrueNAS, Firewall, KVM, and of course RGB ITX Dense Compute with external UPS The minimalist approach: Some builders mount just a mini PC, NAS, and switch with clean cable routing. The simplicity is beautiful—every cable has a purpose, nothing is hidden poorly. This style prioritizes accessibility for maintenance.\nThe dense configuration: Others pack their 8U rack with multiple mini PCs on shelves, a patch panel, managed switches with VLANs, and even with a small UPS. These setups maximize the rack\u0026rsquo;s capacity while maintaining organization. The key insight here is using every 1U strategically.\nThe professional aesthetic: Some setups incorporate brush panels, proper cable management arms, and uniform equipment colors (all black or all silver). These racks look like they belong in a datacenter, just scaled down. The visual consistency signals operational maturity.\nThese setups confirm that the 8U height is the sweet spot for homelab density. Smaller racks feel cramped when you add even modest equipment. Larger racks (12U+) create pressure to fill empty space with unnecessary hardware. The 8U forces intentional choices about what deserves rack mounting.\nThe Minisforum MS-01: My Primary Workhorse # The heart of my setup is a Minisforum MS-01 configured with an Intel Core i9-12900H, 48GB DDR5 RAM, and 1TB NVMe storage.\nWhy Minisforum? I\u0026rsquo;ve watched this brand evolve over the past few years. They\u0026rsquo;re doing something unique in the mini PC space—designing machines specifically for infrastructure use cases rather than just shrinking desktop PCs. The MS-01 has dual 2.5GbE NICs and dual 10GbE NICs, multiple M.2 slots, and a thermal design that handles sustained workloads without throttling. It\u0026rsquo;s clear they understand what engineers need.\nThe i9-12900H with its 14 cores (6 performance, 8 efficiency) is more than enough for my current needs. The CPU rarely becomes the bottleneck. What matters more for my setup is RAM—running Proxmox with multiple VMs simultaneously means memory capacity directly determines how many workloads I can run. The 48GB gives me comfortable headroom, but I\u0026rsquo;d like to max it out at 96GB with another 48GB module. Unfortunately, DDR5 SO-DIMM prices are still crazy high right now. I\u0026rsquo;m waiting for prices to drop before adding the second module.\nThermal performance is solid—no additional cooling required. The acoustic profile is dominated by the spinning disks in the Synology rather than the mini workstation itself, good for home office environments where traditional server equipment would be disruptive.\nMinisforum has proven itself as a serious option for infrastructure work. Their mini workstations deliver the performance, build quality, and thermal characteristics needed for sustained production workloads without the operational overhead of traditional server equipment. The newer models—MS-02, MS-A2, and MS-S1 MAX—continue this trajectory with improved computing power and expansion capabilities.\nProxmox and Kubernetes # Here\u0026rsquo;s how the infrastructure actually works: I run three physical machines as Proxmox nodes, and those Proxmox nodes host multiple VMs that form my K3s cluster. This virtualization layer gives me flexibility to create, destroy, and reconfigure K3s nodes without touching physical hardware.\nThe Minisforum MS-01: My primary Proxmox node. It runs several VMs—some as K3s control plane and worker nodes, others for specific services. Beyond the cluster infrastructure, I also run dedicated Linux development environment VMs and Windows VMs for different use cases I need. The 48GB RAM lets me run many VMs simultaneously without memory pressure.\nThe old laptop (ASUS K43SM): My second Proxmox node. This laptop has been with me for a long time and I no longer use it as a daily driver. It has modest power consumption, and stays within thermal limits. Currently it is only connected to keep the Proxmox quorum.\nThe desktop with GPU: My third Proxmox node, which exists primarily because it has a GPU that I need for AI and ML workloads. At work, I architect ML pipelines for medical imaging systems—training models for 3D image segmentation and registration on AWS and company\u0026rsquo;s servers, and optimizing inference performance. At home, this machine lets me experiment with GPU-accelerated model training, test ML workloads, and validate deployment strategies. The GPU makes it essential for my professional development.\nOn asymmetric nodes: In practice, heterogeneous clusters are common. Different hardware specs, thermal profiles, and capabilities are normal in production Kubernetes environments. Node selectors, taints, and tolerations exist precisely to handle this reality. With proper planning, you can run workloads efficiently across diverse hardware. The laptop, if necessary, can also handle lightweight pods, the MS-01 runs more intensive services, and the desktop takes GPU workloads. It works.\nThat said, I do want to replace the laptop and desktop with additional mini workstations eventually. Not because asymmetric nodes are inherently problematic, but because a more symmetric setup would let me build a high-performance cluster with fast networking between nodes. With dual 10GbE NICs on each machine, I could create a dedicated backend network for storage traffic and cluster communications, keeping management and application traffic separate. That network topology becomes much cleaner with symmetric hardware.\nThe other benefit: replacing the laptop and the bulky desktop with compact mini PCs means the entire infrastructure fits in a single 8U rack. Everything rack-mounted, properly organized, occupying minimal space. That\u0026rsquo;s the end goal—production-grade infrastructure in the smallest footprint possible.\nStorage: The Synology NAS # A Synology DS923+ with 2x 8TB drives provides all persistent storage for the cluster. The DS923+ has four drive bays, so I have room for expansion as storage needs grow. This is the infrastructure component I wish I\u0026rsquo;d bought sooner.\nThe NAS market has evolved since I bought my Synology—brands like Ugreen are now offering competitive alternatives with compelling hardware specs and pricing. Synology\u0026rsquo;s value proposition remains its software ecosystem. DiskStation Manager (DSM) provides enterprise features—NFS, snapshots, incremental backups, and application packaging—with consumer-friendly management interfaces. For production workloads, software maturity and operational reliability outweigh raw hardware specifications.\nK3s includes local storage out of the box, and that works fine for experimentation. But when you\u0026rsquo;re running production services that people depend on, you need network storage with proper backup capabilities.\nThe Synology runs NFS out of the box and integrates cleanly with Kubernetes CSI drivers. I configure PersistentVolumes that let pods migrate between nodes without losing data. The built-in snapshot capabilities protect against data loss, and Synology\u0026rsquo;s RAID configuration means a single drive failure doesn\u0026rsquo;t take down my infrastructure.\nHere\u0026rsquo;s the clever part: the DS923+ isn\u0026rsquo;t just storage. It has enough CPU and RAM to run a VM that can serve as another Proxmox node if I want. I can also run other apps directly on the Synology—for example, using it as a Tailscale node that advertises subnets and works as an exit node. The hardware capability means it can handle multiple roles beyond just being a NAS.\nNetwork Infrastructure # A Gigabit switch ties everything together. At the moment, I\u0026rsquo;m only using a simple TP-Link switch-nothing fancy, but reliable. I also added a GL.iNet GL-MT3000 (Beryl AX) travel router to my gears for secure remote access when I\u0026rsquo;m away from home.\nShort patch cables keep the rack tidy. Quality Cat6a cables in the exact lengths you need (0.5m, 1m) are worth the extra cost over cheap bulk cables you have to coil up.\nPower Consumption: The Real Operating Cost # My electricity consumption runs less than €20 per month. This figure includes only the hardware visible in the rack photos—the MS-01, the Synology NAS, the network switch, and the Hue smart hub. It does not include the laptop and the desktop PC. I track actual wattage with a Tapo smart plug that logs consumption over time.\nThe MS-01 is remarkably efficient for its performance. The Synology is power-efficient by design.\nCompare this to AWS EKS: the control plane alone would run over €70 monthly, and that\u0026rsquo;s before adding any compute nodes or storage. A modest EKS setup with a couple of workers and reasonable storage easily runs several hundred euros per month. My homelab paid for itself in under a year of equivalent AWS costs. After that, every month represents significant savings while giving me hardware I own and infrastructure I control completely.\nLessons from Building This # Start with the rack: Even if you\u0026rsquo;re only mounting one device initially, the physical infrastructure creates operational discipline. The GeeekPi 8U is affordable and sets you up for future expansion without looking ridiculous with a single piece of equipment inside.\nInvest in the primary node: I could have bought three cheaper old mini PCs for the same cost as one MS-01. But having one truly capable machine as the primary node means I never hit performance limitations during experimentation. The other nodes can be more modest-or even repurposed equipment.\nDon\u0026rsquo;t skip the NAS: Local storage works until you need to migrate a pod and realize all its data is stuck on the original node. Network storage with proper backup capabilities is worth the investment.\nPower consumption matters: Before buying equipment, calculate the actual operating cost. A machine that draws 100W more than necessary costs you an extra €120+ annually. Over three years, that\u0026rsquo;s significant money that could have bought better hardware upfront.\nThe hardware matters less than you think. What matters is treating your homelab like production infrastructure—monitoring before you need it, automating backups before you lose data, documenting before you forget your architectural decisions.\nMy GeeekPi rack, Minisforum MS-01, and Synology NAS aren\u0026rsquo;t special. They\u0026rsquo;re good hardware operated with discipline. Running production workloads on infrastructure you own and maintain develops the kind of operational maturity that makes a difference in production environments.\n","date":"January 6, 2026","externalUrl":null,"permalink":"/blog/the-hardware-behind-my-home-kubernetes-cluster/","section":"","summary":"One of the questions I got several times is about what hardware I am actually running.\nYou don’t need enterprise servers to run production infrastructure. The compact 8U rack sitting under my desk handles everything I need. This isn’t a buyer’s guide with exhaustive tier comparisons. Instead, I want to share my actual setup—the mini server rack that transformed my homelab, the mini workstation that became my primary workhorse, and the lessons learned from building infrastructure that people depend on daily.\n","tags":"homelab kubernetes k3s hardware server-rack minisforum synology geeekpi self-hosted infrastructure platform-engineering","title":"The Hardware Behind My Home Kubernetes Cluster","type":"blog"},{"content":"","date":"December 29, 2025","externalUrl":null,"permalink":"/tags/ai-engineering/","section":"Tags","summary":"","tags":"","title":"Ai-Engineering","type":"tags"},{"content":"","date":"December 29, 2025","externalUrl":null,"permalink":"/tags/cloud-architecture/","section":"Tags","summary":"","tags":"","title":"Cloud-Architecture","type":"tags"},{"content":"","date":"December 29, 2025","externalUrl":null,"permalink":"/tags/distributed-systems/","section":"Tags","summary":"","tags":"","title":"Distributed-Systems","type":"tags"},{"content":"","date":"December 29, 2025","externalUrl":null,"permalink":"/tags/production-engineering/","section":"Tags","summary":"","tags":"","title":"Production-Engineering","type":"tags"},{"content":"\u0026ldquo;Kubernetes is overkill for a homelab.\u0026rdquo;\nI hear this sometimes, but after creating and maintaining a Kubernetes cluster that serves my family\u0026rsquo;s digital life, supports my path to becoming a golden kubestronaut, and my work, I\u0026rsquo;ve come to realize that the question isn\u0026rsquo;t whether Kubernetes is overkill—it\u0026rsquo;s whether you want a place to practice platform-style operations end to end, or whether your goals are already met with simpler tooling.\nWhy This Matters # At work, I architect cloud infrastructure for medical imaging systems, manage multi-region AWS deployments, and make infrastructure decisions that affect product reliability. My homelab isn\u0026rsquo;t separate from this work—it\u0026rsquo;s where I validate architectural patterns, test failure scenarios, and develop the operational intuition that informs production decisions.\nWhat turns distributed systems from abstract ideas into something you can reason about under pressure is less a stack of books than time in the hot seat. You can read about failure modes, but debugging an outage because users can\u0026rsquo;t access services is what makes the behavior stick—it\u0026rsquo;s the kind of practice you can grow into.\nThe Real Value Proposition # I think about my homelab as a production system that happens to run at home.\nWhen you treat your homelab as production, you implement monitoring not because a tutorial told you to, but because you need to know when services degrade. You design for failure recovery not as an academic exercise, but because restoring hundreds of thousands of photos and videos from backup is painful enough that you\u0026rsquo;ll architect it correctly the first time.\nThis operational rigor translates directly to work. The infrastructure patterns I validate at home—GitOps workflows, secrets management, storage orchestration—are the same patterns I implement in production medical imaging systems where downtime affects patient care. The confidence that comes from running these systems 24/7 makes me a better architect.\nBeyond Container Orchestration # K8s isn\u0026rsquo;t about running containers efficiently. It\u0026rsquo;s about building platforms that abstract infrastructure concerns from application concerns. That perspective separates tactical from strategic thinking.\nIn my homelab, I don\u0026rsquo;t think about deploying one-off apps; I think about how the platform supports a constantly growing fleet of services. I care about how the platform handles persistent storage, how Cloudflare Tunnels route traffic securely, and how backup systems protect state. Applications become declarative intent; the platform handles execution while I experiment with more and more of the CNCF cloud-native landscape.\nThe whole cluster is resilient in a way that ad-hoc containers or other self-hosted setups rarely are. Power down because of a move or a blackout, just power on—everything comes back. No need to sit down with a monitor and keyboard to start services or remember what was running. The control plane reconciles against the declared state and brings workloads up. With a pile of Docker Compose stacks or manually managed services, you either log in and start things by hand or maintain your own bring-up automation; with Kubernetes, that behavior is built in.\nReliability Patterns at Home # My homelab runs a broad mix of services that people depend on. There are media, photo, developer, observability, data, and automation workloads with multiple users, real data, and 24/7 expectations. The cluster keeps growing as I add more internal tools and platform capabilities.\nThese aren\u0026rsquo;t toy applications—they\u0026rsquo;re live services with real users and real consequences for failure. With multiple users using on my infrastructure, I\u0026rsquo;ve had to implement the same reliability patterns I use at work: automated backups with tested restore procedures, monitoring and alerting before users notice degradation, and high availability configurations to survive node failures. When something breaks, I can\u0026rsquo;t file a support ticket. I have to understand the system deeply enough to diagnose and fix it quickly. The cluster constantly evolves as I try out more of the CNCF cloud-native landscape and turn successful experiments into long-lived services.\nThis forces you to think about reliability engineering. Some workloads store irreplaceable personal data, so I run automated regular backups to NFS storage with periodic verification restores. Other services need consistent uptime, so I\u0026rsquo;ve configured pod anti-affinity to spread replicas across nodes. Monitoring alerts me to storage capacity issues and service degradation before anyone complains. These aren\u0026rsquo;t academic exercises-they\u0026rsquo;re operational requirements.\nThe K3s Choice # K3s maintains full Kubernetes API compatibility in a fraction of the resource footprint. It can run on as little as 512MB RAM where full K8s typically needs 2GB or more per node. Installation is a single binary with no kubeadm complexity. It includes a local storage provider out of the box, and integrates cleanly with external ingress solutions.\nMore importantly, K3s forces architectural discipline. With limited resources, you can\u0026rsquo;t be sloppy about requests and limits. You think carefully about what runs where. These constraints teach you to build efficient systems—lessons that apply even when you have unlimited cloud resources.\nI\u0026rsquo;ve run both full K8s and K3s in different contexts. For homelab use, K3s is the right choice unless you specifically need to practice kubeadm or your work uses distributions like OpenShift that diverge from upstream.\nWhat You Actually Learn # The technical skills are obvious—deploying multi-tier applications, configuring ingress and service mesh, managing persistent storage with PV/PVC and CSI drivers, implementing RBAC and network policies, setting up Prometheus and Grafana for observability. You learn Terraform for infrastructure, Helm for packaging, GitOps for deployment workflows.\nBut the deeper value is operational maturity. You develop intuition about system behavior. You understand resource contention not from documentation but from debugging performance issues. You internalize how networking layers interact because you\u0026rsquo;ve traced packet flows through CNI plugins, service meshes, and ingress controllers.\nYou learn to think in layers of abstraction. Applications sit on Deployments sit on ReplicaSets sit on Pods sit on container runtimes sit on nodes sit on hypervisors. When something breaks, you know which layer to investigate. That systems thinking—seeing how the layers fit together—is one of the main payoffs of running the stack yourself.\nThe Investment Analysis # My current setup consists of three PCs with more than enough compute to run my workloads comfortably. I use a compact GeeekPi server rack that\u0026rsquo;s surprisingly elegant for a homelab. The rack is tiny enough to fit in any room without looking like a datacenter, yet it\u0026rsquo;s modular and extendable for future growth. As my needs evolve, I can scale up to a genuinely powerful cluster without replacing the infrastructure. I\u0026rsquo;ll cover the hardware selection and rack setup in detail in a future post.\nCompare this to managed Kubernetes on AWS, which would cost significantly more just for the control plane and modest worker nodes. Plus you don\u0026rsquo;t actually own the infrastructure, so there\u0026rsquo;s no learning at the hypervisor or networking layers.\nTime investment is trickier to quantify. Initial setup took me several weeks of evenings, but I was building documentation and automation as I went. Maintenance typically averages a few hours per month—cluster upgrades, application updates, occasional troubleshooting. Experiments and improvements take as much time as I want to invest.\nThe return isn\u0026rsquo;t measured in dollars—it\u0026rsquo;s measured in architectural capability and operational confidence. I make better infrastructure decisions at work because I\u0026rsquo;ve validated the patterns at home. I\u0026rsquo;m more effective in incident response because I\u0026rsquo;ve debugged similar issues in my homelab.\nWho Benefits From This # You should consider running user-facing infrastructure at home—something you operate as if failure mattered—if you architect systems for a living or want to. Platform engineers, SREs, infrastructure architects, ML engineers who deploy their own models—anyone whose work requires understanding how distributed systems behave in production.\nThis isn\u0026rsquo;t for everyone. If you\u0026rsquo;re purely focused on application development and don\u0026rsquo;t care about the infrastructure layer, Docker Compose is simpler and perfectly adequate. If you prefer managed services and don\u0026rsquo;t want to think about cluster operations, that\u0026rsquo;s a valid choice.\nBut if you\u0026rsquo;re making architectural decisions that affect reliability, scalability, or operational cost—or you want to grow into that role—running that kind of stack at home, with the same discipline you\u0026rsquo;d bring to a live environment, helps you build these capabilities.\nPractical Guidance for Starting # Start with a single node running K3s. Pick one real application that you or your family will actually use—not a demo app. Deploy it, monitor it, and keep it running reliably. Only after you\u0026rsquo;ve mastered single-node operations should you expand to multi-node for HA.\nDon\u0026rsquo;t try to implement everything at once. Setting up service mesh, observability stack, GitOps tooling, and multiple applications simultaneously might bee too much. Build incrementally. Get something working and reliable, then add complexity as you need it.\nDocument everything as infrastructure as code. Terraform for the cluster itself, Helm charts or raw manifests for applications, GitOps workflows for deployment. If you can\u0026rsquo;t recreate your cluster from git clone and a few commands, you\u0026rsquo;re doing it wrong. This discipline pays dividends when you inevitably need to rebuild.\nJoin communities where people run serious homelabs. Reddit\u0026rsquo;s r/homelab and r/kubernetes, the CNCF Slack, local Kubernetes meetups. Learn from people who\u0026rsquo;ve solved problems you haven\u0026rsquo;t encountered yet.\nThe Core Insight # Running Kubernetes at home isn\u0026rsquo;t about the technology—it\u0026rsquo;s about developing the operational mindset that separates engineers who understand systems from engineers who use systems.\nYou can\u0026rsquo;t learn this from courses or certifications. You have to run something that matters, face real failures with real consequences, and build the muscle memory that only comes from repeated operational cycles. When your monitoring alerts you to a problem before your users notice, when you recover from failure efficiently because you\u0026rsquo;ve practiced it, when you make architectural decisions based on operational experience rather than documentation—that\u0026rsquo;s when you\u0026rsquo;ve internalized what it means to own services where failure isn\u0026rsquo;t theoretical.\nMy homelab has made me a better engineer. Not because I practice Kubernetes, but because I developed operational maturity that comes from running production-style systems. That\u0026rsquo;s the real value proposition.\n","date":"December 29, 2025","externalUrl":null,"permalink":"/blog/why-kubernetes-in-a-homelab-and-why-you-should-consider-it-too/","section":"","summary":"“Kubernetes is overkill for a homelab.”\nI hear this sometimes, but after creating and maintaining a Kubernetes cluster that serves my family’s digital life, supports my path to becoming a golden kubestronaut, and my work, I’ve come to realize that the question isn’t whether Kubernetes is overkill—it’s whether you want a place to practice platform-style operations end to end, or whether your goals are already met with simpler tooling.\n","tags":"kubernetes k3s homelab platform-engineering sre infrastructure distributed-systems production-engineering ai-engineering cloud-architecture self-hosted","title":"Why Kubernetes in a Homelab? (And Why You Should Consider It Too)","type":"blog"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","tags":"","title":"Categories","type":"categories"},{"content":"This section contains guides and how-tos for the homelab insfrastructure setup and self-hosted services. It is meant as a detailed reference for what I have tried and confirmed to work, so I can quickly search and look things up in the future.\nThe built-in search works very well and makes finding specific topics fast and convenient. If this documentation helps others, that is a nice bonus. I also have many more notes and documents in my project repositories and I am gradually moving them here, so this page will become much more complete and detailed over time.\n[On mobile, the sidebar is collapsed; use a desktop-sized layout to see the full documentation navigation.]\n","externalUrl":null,"permalink":"/docs/","section":"Documentation","summary":"This section contains guides and how-tos for the homelab insfrastructure setup and self-hosted services. It is meant as a detailed reference for what I have tried and confirmed to work, so I can quickly search and look things up in the future.\nThe built-in search works very well and makes finding specific topics fast and convenient. If this documentation helps others, that is a nice bonus. I also have many more notes and documents in my project repositories and I am gradually moving them here, so this page will become much more complete and detailed over time.\n","tags":"","title":"Documentation","type":"docs"}]