Imported from joryirving/home-ops (
AGENTS.md). Install upstream withnpx skills add joryirving/home-ops. Copyright stays with the author.
Home Operations - AI Assistant Guide
This is a Home Kubernetes cluster monorepo managed with GitOps (Flux, Renovate, GitHub Actions).
Repository Structure
home-ops/
├── .agents/ # AI instructions & skills
│ ├── instructions/ # PR review system prompt, YAML sorting rules
│ └── skills/ # Reusable agent skills (e.g. add-app)
├── .github/ # GitHub Actions workflows & evidence providers
├── .renovate/ # Local Renovate config presets
├── .taskfiles/ # Task (taskfile.dev) operational commands
├── bootstrap/ # Bootstrap templates (helmfile, minijinja)
├── docs/ # mdBook documentation
├── hack/ # Operational scripts (see hack/README.md)
├── kubernetes/ # Kubernetes configurations (Flux-managed)
│ ├── apps/ # Application configs
│ │ ├── base/ # Shared base configs
│ │ ├── main/ # Main cluster overlay
│ │ ├── utility/ # Utility cluster overlay
│ │ └── test/ # Test cluster overlay
│ ├── clusters/ # Flux cluster definitions
│ └── components/ # Reusable k8s components
├── talos/ # Talos Linux machine configs
└── terraform/ # OpenTofu/Terraform IaC (cloud infra)
Cluster Architecture
- main - 3x MS-01 + 1x Bosgame M5 (i9-13900H x3, Ryzen AI Max+ 395 x1, 128GB RAM), hyper-converged storage
- utility - 1x Bosgame P1 (Ryzen 7 5700U), low-power services
- test - 1x Beelink Mini-S (Celeron N5095), testing
Key Technologies
| Category | Tool | Purpose |
|---|---|---|
| GitOps | Flux + flux-operator | Deploys configs from Git to k8s; flux-operator manages the Flux instance itself |
| CI | Renovate + GitHub Actions | Dependency updates, automation |
| Networking | cilium (eBPF) | CNI, BGP, service mesh |
| Ingress | Envoy Gateway | L7 proxy, ingress controller |
| DNS | external-dns | Syncs ingress to Cloudflare/UniFi |
| TLS | cert-manager | TLS certificate automation |
| Secrets | external-secrets + 1Password | Secret management |
| Storage | Rook/Ceph + volsync | Distributed storage + backups |
| Images | spegel | Local OCI mirror |
| IaC | tofu-controller | Terraform on k8s |
| Charts | app-template (bjw-s) | Common Helm chart used by most apps |
| Sources | OCIRepository | Flux source for OCI Helm charts (preferred) |
| Reviews | konflate | Rendered-diff evidence provider for PR reviews |
GitOps Flow
Git push → Flux source sync → Kustomization → HelmRelease → k8s resources
Flux starts from kubernetes/apps/<cluster>/ overlays. App manifests live in kubernetes/apps/base/<namespace>/<app>/; each cluster overlay is a Flux Kustomization at kubernetes/apps/<cluster>/<namespace>/<app>.yaml pointing to that base directory. Namespace is declared once per namespace in kubernetes/apps/<cluster>/<namespace>/kustomization.yaml; kubernetes/components/replacements/ks.yaml copies it to each overlay's spec.targetNamespace. A change under kubernetes/apps/base/ can affect every cluster that includes the app.
Conventions
- Component READMEs stay with components (e.g.,
kubernetes/apps/base/cilium/README.md) - Secrets stored in 1Password, referenced via
external-secrets - Apps use
HelmReleasevia Flux, rarely raw manifests - Clusters are mostly identical except for app selections and sizing
- AI instructions:
.agents/instructions/pr-review.instructions.mdis the live system prompt for the AI PR reviewer..agents/instructions/sorting.instructions.mddefines YAML sorting rules (includingapp-template-specific ordering)..agents/instructions/foreman.instructions.mddocuments the Foreman/LLMKube coding loop. When editing YAML, follow the sorting instructions; read the Foreman instructions before changingkubernetes/apps/base/llm/foreman/, LLMKube, or dispatch configuration. - Namespace component:
kubernetes/components/namespace/injects the Namespace resource and alerting rules into every app via kustomize components. Helm chart sources are per-app: each app declares its ownOCIRepositoryinocirepository.yaml. - Namespace replacement:
kubernetes/components/replacements/ks.yamlpropagatesspec.targetNamespaceinto Flux Kustomizations automatically. - New apps: Follow
.agents/skills/add-app/SKILL.mdfor the complete workflow; create manifests inbase/and an overlay in each requested cluster. - Postgres:
kubernetes/components/postgres/is the CloudNativePG component. Its README defines recovery bootstrap and thecomponents.postgres/cnpg=initlabel for net-new databases. Treat changes to CNPGClusterresources or bootstrap labels as data-loss-relevant.
Common Operations
-
Add app: Create base manifests in
kubernetes/apps/base/<namespace>/<app>/, then cluster overlayKustomizationfiles for the requested clusters -
Update app: Merge renovate PR or manually edit and push
-
Troubleshoot: Check
flux get all -n <namespace>,kubectl get events --sort-by=.lastTimestamp -
Scripts:
hack/contains operational scripts. Seehack/README.mdfor the full list and usage. -
Task operations: The repo is driven by Taskfile. Run
task --listto see all commands. Common tasks:task talos:apply-node CLUSTER=main NODE=<node>— apply Talos configtask talos:upgrade-k8s CLUSTER=main VERSION=<ver>— upgrade Kubernetestask kubernetes:reconcile CLUSTER=main— force Flux reconciliationtask kubernetes:hr-restart CLUSTER=main— restart failed HelmReleasestask bootstrap:talos CLUSTER=main— bootstrap a fresh Talos clustertask op:push/task op:pull— sync kubeconfig/talosconfig with 1Passwordtask workstation:brew— install local workstation tools
-
Tool management:
.mise.tomlpinsflate; runmise installto set it up. Other task preconditions identify their required tools. -
Validate locally: Run
flatebefore pushing GitOps changes:# Test Kustomizations + HelmReleases for a cluster flate test all --path ./kubernetes/clusters/main # Diff against a baseline (e.g., main branch) git worktree add --detach /tmp/baseline origin/main flate diff ks --path ./kubernetes/clusters/main --path-orig /tmp/baseline/kubernetes/clusters/main flate diff hr --path ./kubernetes/clusters/main --path-orig /tmp/baseline/kubernetes/clusters/main git worktree remove /tmp/baseline --force -
Gateway policy namespace rule:
ClientTrafficPolicyandEnvoyPatchPolicythat target aGatewaymust live in the same namespace as thatGateway. Forenvoy-internal, put those resources inkubernetes/apps/base/network/envoy-gateway/config/with namespacenetwork. See that directory for examples.
Documentation
- Main docs:
/docs/src/(mdBook) - Component docs: README files co-located with components
- Terraform docs:
/terraform/tofu.md - Personal notes:
/docs/src/notes/
Adding Documentation
When adding architecture or operational docs, consider:
- Put user-facing docs in
/docs/src/ - Keep component-specific docs with the component
- Personal notes go in
/docs/src/notes/
PR Review Standards
When reviewing Renovate PRs, enforce these criteria. Reviews may include konflate rendered-diff evidence (cluster impact, data-loss cautions, image changes). Treat blocker-level findings as high-priority signals.
HelmRelease Requirements
- All applications MUST use
HelmReleasevia Flux, not raw manifests - HelmReleases MUST use
spec.chartRefpointing to anOCIRepositorywith a pinnedref.tag. - Every app (including
app-template-based apps) defines its own per-appOCIRepositoryin a dedicatedocirepository.yamlalongside theHelmRelease, named after the app, with./ocirepository.yamllisted in the app'skustomization.yaml. Do not put theOCIRepositoryinline inhelmrelease.yaml, and do not rely on a shared/injectedOCIRepository. - Must include
spec.intervalfor reconciliation frequency - Resource limits (CPU/memory) SHOULD be specified for production workloads, but this is not a hard requirement
valuesFromshould reference ConfigMaps/Secrets, not inline values
Namespace Convention
metadata.namespaceis never set inline onHelmReleaseorKustomizationresources — this is intentional, not a violation- The namespace is injected at build time by kustomize's
namespace:directive in the per-appkustomization.yaml(e.g.,namespace: llm) - For Flux
Kustomizationresources,spec.targetNamespaceis propagated automatically via the replacement component atkubernetes/components/replacements/ks.yaml - Reviewers MUST NOT flag missing
metadata.namespaceon these resources as an issue
Secret Management Rules
- NEVER commit plain-text secrets or credentials in Git
- All secrets MUST use
external-secretswith 1Password backend - If a PR introduces a new secret, verify it's external-secrets backed
- Talos machine configs (
talos/*/machineconfig.yaml.j2) storeop://references in Git that are resolved at runtime viaop inject. This is the intended pattern for machine-level secrets; do not replace them withexternal-secrets
Image & Digest Policy
- Prefer
@sha256:digests over version tags for reproducibility (container images only) - OCI artifacts (e.g., Helm charts pulled via
OCIRepository) are exempt: pin by tag/version, since they don't support SHA-tag references the same way container images do - For tag-only updates, verify OCI metadata (revision/source/created)
- If revision changes between digests, ensure it's intentional
- Reject updates from untrusted registries (must be allowlisted)
- Preferred registries: GHCR.io, registry.k8s.io, Docker Hub (fallback)
- Avoid Docker Hub for critical infrastructure components
Cluster-Specific Policies
- main cluster (production): Strict validation - all standards must be met
- utility cluster (low-power services, production): Strict validation - all standards must be met
- test cluster (testing): Can accept bleeding-edge versions, still enforce secrets policy
Breaking Change Detection
Always request_changes if:
- API version changes (e.g.,
apiVersion: apps/v1beta1→apps/v1) - Deprecated field usage introduced
- Major version bumps without justification
- CRD changes or custom resource modifications
- Network policy or security context relaxations
- A rendered diff introduces a
suspendfield or other Flux suspension artifact that was absent at merge-base
Required Evidence for Approval
Before approving, verify:
- Release notes/changelog mention the upgrade
- GitHub compare shows expected changes
- Version aligns with what Renovate reported
- No breaking changes identified in release notes
- Security advisories don't apply to this version
For Helm chart and container image upgrades, you must use tool requests (e.g., gh_api) to fetch release notes, changelogs, and upstream metadata from the source repository. Do not rely on the PR description alone — verify against the actual upstream release. The AI review workflow also provides Konflate's rendered Flux diff and upgrade-impact evidence; use them to establish the real blast radius, but treat unavailable advisory evidence as Unknown rather than a clean result.
Kubernetes ↔ Talos compatibility
This cluster runs on Talos Linux, which pins the node OS and the kubelet together. Kubernetes/Talos upgrade PRs may touch talos/*/machineconfig.yaml.j2 and kubernetes/apps/*/kube-tools/upgrades/{talosupgrade,kubernetesupgrade}.yaml across multiple clusters. For each affected cluster, read the Talos version from the installer.image entry in the UnattendedInstallConfig document in talos/<cluster>/machineconfig.yaml.j2 (format: factory.talos.dev/metal-installer/<schematic>:<version>). When reviewing one, you MUST:
- Identify every affected cluster and its Talos installer image.
- Confirm that cluster's new Kubernetes version is supported by its Talos release against Talos's published support matrix at
docs.siderolabs.comorwww.talos.dev. - Cite the matrix in the review. Do not approve a Kubernetes bump on "patch release" reasoning without confirming Talos supports it — an unchecked matrix is an Unknown, not an approval.
Flux automatically reconciles changes once the PR is merged.