NixOS, k3s, and OpenTofu configuration for my personal homelab. Johto manages hosts, applications, private networking, Cloudflare DNS, and backups.
See also Hoenn for personal machines, Sinnoh for production services, and Kanto for shared inference.
| Host | Role |
|---|---|
olivine |
k3s server, public ingress, private DNS, observability, and etcd backups |
goldenrod |
k3s worker, media services, persistent storage, NFS, Garage, and application backups |
The nodes communicate over WireGuard. Tailscale provides private access, and public services enter through Olivine. Workloads include Immich, Nextcloud, Paperless, Pocket ID, Plex, Jellyfin, Servarr, CloudNativePG, Prometheus, Loki, and Uptime Kuma.
Comin reconciles host configurations from master. Flux reconciles the
Kubernetes resources in k8s/flux-system/ from the same branch. OpenTofu
manages Cloudflare DNS separately.
nix/
├── hosts/nixos/ Host configuration and hardware state
└── modules/ Host defaults and shared features
k8s/ Flux and application manifests
secrets/ SOPS-encrypted host secrets
keys/ Public SOPS recipients
terraform/ OpenTofu configuration for Cloudflare DNS
docs/ Service operations
flake.nix exposes the olivine and goldenrod NixOS configurations.
Kubernetes secrets live in k8s/secrets/.
Enter the pinned development shell with nix develop, or run direnv allow to
load it automatically. Then run:
nix fmt
nix flake checkBuild each host affected by a Nix change:
nix build .#nixosConfigurations.olivine.config.system.build.toplevel
nix build .#nixosConfigurations.goldenrod.config.system.build.toplevelShared validation and metrics helpers come from the pinned dvnpkgs input.
After Kubernetes changes, run just check-k8s. For OpenTofu changes, run
tofu -chdir=terraform init on a fresh checkout, then run:
tofu -chdir=terraform fmt -check
tofu -chdir=terraform planRun just to list the other maintenance commands.
Commit and push validated changes, then let Comin and Flux reconcile master.
Run just flux-status to inspect the cluster through the johto context.
Review the OpenTofu plan before applying DNS changes. The B2 state backend does not lock state, so never run concurrent applies.
The observability runbook explains how to check metrics and logs.
Run just sops-bootstrap once per machine to derive your local age key from
your SSH key. Then edit secrets with SOPS:
just sops-edit tailscale.yaml
just sops-edit k8s/secrets/pg-shared-b2.sops.yamlDirenv loads the encrypted Cloudflare and Backblaze credentials for OpenTofu.
After changing keys/, run just sops-rekey. Commit .sops.yaml and all
re-encrypted files together. Never commit decrypted secrets, private keys,
OpenTofu state, or saved plans.
Licensed under the MIT License.