Adapting This for Your Cluster¶
This repository documents one specific homelab. Node names, addresses, the
wlkr.ch domain, and the Git repository URL are hardcoded throughout
payload/ and ansible/. Run the Quickstart unchanged and you
get a cluster that syncs from this repository and requests certificates for a
domain you don't control.
Everything below is a change you make in your own fork, before step 1 of the Quickstart.
1. Fork and repoint ArgoCD¶
Every ArgoCD Application points at this repository by URL. Until you change
them, your cluster pulls its desired state from here and your own commits have
no effect.
Rewrite them to your fork. The match ends in .git, so the workloads
repository below is left alone:
git grep -lz 'github.com/JanWelker/homelab.git' -- payload/ | \
xargs -0 sed -i '' 's|github.com/JanWelker/homelab.git|github.com/YOUR_USER/YOUR_REPO.git|g'
The workloads repository is a second URL, in
payload/platform/argocd-projects/projects.yaml (the apps project's
sourceRepos) and payload/workloads/applicationset.yaml. Point both at your
fork of
homelab-apps.
Note
sed -i '' is the BSD/macOS form. On Linux use sed -i with no argument.
If your fork is private, ArgoCD also needs repository credentials — see the ArgoCD private repository docs.
2. Choose your domain¶
Four hostname patterns carry traffic. Each is a listener in
payload/platform/gateway-api/gateways.yaml and a zone in external-dns's
domainFilters (payload/platform/external-dns/application.yaml); the HTTPS
ones also have a wildcard Certificate in
payload/platform/certificates/certificates.yaml:
| Pattern | Purpose | In a fork |
|---|---|---|
*.k8s.<your-domain> |
user-facing workloads, via apps-gateway |
rename |
*.infra.k8s.<your-domain> |
platform UIs (ArgoCD, Grafana, Hubble, OpenBao, Rook), via infra-gateway |
rename |
*.ssh.<your-domain> |
the SSH listener for in-cluster agent sessions | rename, or delete the listener and the zone |
*.wollbi.ch |
a second site family on apps-gateway, with its own listener and certificate |
delete or rename every file git grep -l wollbi -- payload/ lists |
Replace the first three in one pass; wollbi.ch does not match the pattern
and is handled by hand:
That covers the Gateway hostnames, the wildcard Certificate resources, every
HTTPRoute, ArgoCD's global.domain, and the
link.argocd.argoproj.io/external-link annotations.
You must own this domain: the certificates are issued by Let's Encrypt through
a DNS-01 challenge, which requires write access to the zone. .local, .lan
and made-up TLDs will not work.
3. Point DNS at the gateway IPs¶
The two Gateways take fixed addresses from the Cilium pools in
payload/platform/cilium/lb-pools.yaml:
| Gateway | DNS record |
|---|---|
apps-gateway |
*.k8s.<your-domain> |
infra-gateway |
*.infra.k8s.<your-domain> |
Change both CIDRs to free addresses on your LAN, outside the DHCP range, or
the router will eventually hand one to a laptop, then create the two wildcard
A records. Two more things in the same component decide whether anything
answers on those addresses:
- The
CiliumL2AnnouncementPolicyinlb-pools.yamlanswers ARP for them from the control-plane nodes only, on interfaces matching a name pattern. Change thenodeSelectorif your control planes should not carry the traffic andinterfacesif the pattern does not match your NICs; L2 needs the addresses in the nodes' subnet. payload/platform/cilium/bgp.yamlalso announces them to the router over eBGP, and its peer and ASNs must match the inventory (step 5). With no BGP router, delete the file.
4. Set up the DNS-01 solver¶
payload/platform/certificates/cluster-issuers.yaml is written for AWS
Route53. Update:
email:— your address, on both issuers. Let's Encrypt sends expiry notices here.region:— the Route53 region.
If your DNS is hosted elsewhere, replace the dns01.route53 solver with the
matching cert-manager DNS-01 provider
and adjust the credential path in OpenBao accordingly.
Tip
Switch issuerRef in certificates.yaml to letsencrypt-staging while you are still iterating. Production allows 5 duplicate certificates per week, and a misconfigured solver burns through that in minutes.
5. Describe your hardware¶
Edit ansible/inventory.yaml; every variable is listed under
Quickstart → step 2. The ones a fork must choose
rather than copy:
| Setting | Notes |
|---|---|
control_plane_vip |
Free address on the nodes' subnet, outside DHCP and distinct from the LoadBalancer pools. Becomes the API endpoint in the cluster certificates. |
control_plane_vip_interface |
The NIC kube-vip advertises on; check ip link on a provisioned node. |
control_plane_endpoint_host |
Set a name on a fresh build, see Endpoint name; a running cluster keeps the VIP. |
| host entries | Replace the hosts with yours. Each needs ansible_host and mac_address (the NIC that PXE boots), and a DHCP reservation that pairs the two: the node takes its address from DHCP, and the kubeadm configs are rendered from ansible_host — see Quickstart → Other requirements. |
router_ip, router_asn, cluster_asn |
The eBGP peer for the Gateway addresses; payload/platform/cilium/bgp.yaml must name the same values, since the payload cannot read the inventory — see Cilium. |
Then update payload/platform/cilium/values.yaml:
k8sServiceHost— a literal, reachable API server address, because Cilium replaceskube-proxyand cannot reach the API through a Service — see Design Decisions → Cilium.devices— the interface prefix Cilium binds to; checkip linkon a provisioned node.
And payload/platform/kube-vip/daemonset.yaml, which takes over from the
bootstrap static pod once ArgoCD syncs and cannot read the inventory:
address— the same value ascontrol_plane_vip.vip_interface— the same value ascontrol_plane_vip_interface.
A mismatch here does not fail at provisioning. It fails on the first sync, when the DaemonSet removes the static pods and starts advertising somewhere else.
6. Provisioning access¶
Both Butane templates, ansible/templates/butane_node_config.yaml.j2 and
butane_installer_config.yaml.j2, inject ~/.ssh/id_ed25519.pub as the
authorized key for the core user. Point both at your own key if you use a
different path or algorithm. There is no password,
no console login, and no rescue path short of reprovisioning, so this is the
only way into the nodes afterwards.
7. The documentation site¶
If you want your fork to publish its own copy of these docs, update
zensical.toml: site_url, repo_url, repo_name, and copyright, and
docs/CNAME, which binds the Pages site to a custom domain; delete it if you
have none. The docs.yaml workflow then publishes to your own GitHub Pages. Otherwise, delete
.github/workflows/docs.yaml to stop the build from running.
Checklist¶
Before make config:
- [ ]
repoURLpoints at your fork in all ofpayload/, and the workloads URLs at yours - [ ] Domain replaced throughout
payload/,wollbi.chrenamed or removed - [ ] LoadBalancer IPs are free addresses on your subnet, and the L2 policy or BGP peer announces them
- [ ] Wildcard DNS records created for both gateways
- [ ] ACME email and DNS-01 provider match your setup
- [ ]
inventory.yamldescribes your nodes, with the rightboot_server_ip - [ ]
control_plane_vipis free, andcontrol_plane_vip_interfacematches the NIC - [ ]
addressandvip_interfacein the kube-vip DaemonSet match those two - [ ]
k8sServiceHostanddevicesmatch your control plane and NICs. On a new build, pointk8sServiceHostatcontrol_plane_viponce the VIP answers — see Control Plane VIP - [ ] SSH public key path is correct
- [ ] Changes committed and pushed — ArgoCD reads from Git, not your working tree