Running production k3s on ARM with GitOps
The orchestration layer behind Badger Cloud is small on purpose: lightweight Kubernetes, ARM compute, and Git as the single source of truth.
Why k3s, and why ARM
We didn't need a sprawling managed Kubernetes service to run a focused e-commerce platform. We needed something dependable, cheap to operate, and efficient. That led us to k3s — a fully CNCF-certified Kubernetes distribution packaged as a single lightweight binary — running on ARM64 (Ampere-class) nodes.
ARM gives us strong performance-per-watt, which keeps both the bill and the energy draw down (the case for that is on our sustainability page). k3s keeps the control-plane overhead small enough that a modest node can run it comfortably, leaving the worker nodes free for actual workloads.
Git is the source of truth
Every piece of the platform — workloads, monitoring, ingress, even this landing page
— is declared as YAML in a single Git repository. Flux runs inside the
cluster and continuously reconciles the live state to match the repository. There is no
manual kubectl apply in normal operation: you open a merge request, it gets
validated, it merges, and the cluster converges.
- Auditable — the Git history is the change log for the platform.
- Reproducible — the repository can rebuild the cluster's desired state from scratch.
- Safe — changes are validated against Kubernetes schemas in CI before they ever reach the cluster.
We use Kustomize overlays to keep a clean separation between base manifests and per-environment configuration (development vs production), so the difference between environments is explicit and reviewable rather than hidden in scripts.
The interesting gotcha: an IPv6-only worker fleet
To keep costs and address usage lean, our worker nodes are IPv6-only, with the control-plane node acting as a NAT gateway for outbound IPv4. It's efficient, but it creates a genuine dependency worth designing around: when the control plane restarts, the workers briefly lose outbound IPv4 connectivity.
The lesson is to treat that node's maintenance as a deliberate, planned operation — drain, reboot in the quiet window, uncordon — and to keep application workloads on the workers so they keep serving traffic throughout. It's a reminder that "efficient" and "naive" aren't the same thing: the savings are real, but only if the failure mode is understood and handled.
Keeping it current, automatically
The cluster largely maintains itself. Worker nodes are upgraded by an in-cluster upgrade controller following a pinned plan; OS patching is automated; and node reboots are coordinated through a dedicated reboot daemon that operates only within a defined overnight window and announces itself to our team chat. The control-plane upgrade stays a deliberate, manual step — precisely because of the NAT-gateway dependency above.