Live
Homelab platform
One small server running Incus, managed entirely as code: VMs, networks, a k3s cluster, GitOps apps, tailnet ACLs and hardened app VMs.
Source lives on my private, self-hosted Forgejo; I can walk you through it on a call.
What it is
My personal infrastructure lab and the place I try out platform ideas end to end. It replaced a bare Ubuntu + k3s box with an Incus host where every VM, container, network and profile is declared in OpenTofu, and every in-cluster app is reconciled by Flux from the same repository. It also hosts my self-hosted Forgejo and its CI runners.
How it is set up
- Hierarchical Terragrunt config (
root → provider → environment → region → service), with small hand-writtenfor_eachmodules for Incus projects, profiles, networks and instances. Stacks are numbered and depend on each other, sorun --allapplies them in order. - Network: the host sits on a dedicated router VLAN. VMs attach with macvlan and get real addresses from the router. Remote access goes through one unprivileged container acting as a Tailscale subnet router; who can reach what is enforced by the tailnet ACL, which is also code.
- k3s lab cluster (control plane + worker VMs) bootstrapped with Ansible, then handed to Flux in three layers: controllers → configs → apps.
- Day-2 operations are automated: Renovate opens PRs for chart, image and version pins; system-upgrade-controller rolls k3s one node at a time; kured reboots nodes only inside a 04:00–05:00 window; non-cluster hosts get unattended upgrades.
- Hardened app VMs for Docker Compose workloads: an nftables egress allow-list Docker cannot bypass, an SSH user that can only run one validated deploy command (pull by digest, roll back on a failed healthcheck), and age-encrypted backups.
Worth pointing out
The box is small on purpose. Sizing profiles carry the headroom maths, Longhorn runs one replica because there is one real worker, and nothing gets HA topology it does not need. Every change goes plan → review → apply; nobody kubectl applys a Flux-managed object.