config/skills/infra/home-ops-deployer/SKILL.md
Deploy and manage applications in the home-ops Kubernetes cluster via GitOps. Use when deploying new apps, modifying existing ones, adding routing, managing secrets, or working with the home-ops repo structure.
npx skillsauth add gavinmcfall/agentic-config home-ops-deployerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Everything reaches the cluster through Git. Flux reconciles. Manual changes revert.
kubectl apply. Commit to ~/home-ops, push, Flux deploys.task configure. It is for initial bootstrap only. Edit both the template AND the generated output file manually.tag@sha256:digest. Use crane digest to fetch.bjw-s/app-template chart unless a vendor chart is required (infrastructure components).You edit files in ~/home-ops → push to GitHub → Flux detects (≤30min or webhook)
→ Flux applies to cluster → cluster converges to match Git
Two kinds of files:
bootstrap/templates/*.yaml.j2) — Jinja2 source, uses #{var}# syntaxkubernetes/apps/**/*.yaml) — what Flux actually readsIf a template exists for a file, edit BOTH the template AND the generated file. If no template exists, edit the generated file directly.
kubernetes/apps/<namespace>/<app>/
├── ks.yaml # Flux Kustomization
└── app/
├── helmrelease.yaml # HelmRelease (app-template)
├── kustomization.yaml # Lists resources for Kustomize
└── secret.sops.yaml # Encrypted secrets (optional)
Wire into namespace: add ./myapp/ks.yaml to kubernetes/apps/<namespace>/kustomization.yaml.
These are the values Claude hallucinates. Use the correct ones.
| Pattern | Correct | Wrong |
|---------|---------|-------|
| Secret store | onepassword-connect | onepassword, 1password |
| Block storage | ceph-block | ceph-block-storage |
| Filesystem storage | ceph-filesystem | cephfs |
| Hostname | {{ .Release.Name }}.${SECRET_DOMAIN} | appname.${SECRET_DOMAIN} |
| Timezone | ${TIMEZONE} | Pacific/Auckland |
| Gateway name | internal or external | envoy-internal, envoy-external |
| Gateway namespace | network | default, networking |
| Gateway section | https | http |
| Default UID/GID | 568 | 1000 |
| Image tag | v1.0@sha256:abc... | latest, v1.0 |
| Makejinja var | #{variable}# | {{variable}} |
See references/deploy-app.md for the full step-by-step process.
Quick summary:
kubernetes/apps/<namespace>/<app>/helmrelease.yaml using app-template patternkustomization.yaml listing all resourcesks.yaml (Flux Kustomization)kustomization.yamltask kubernetes:kubeconformroute:
app:
annotations:
internal-dns.alpha.kubernetes.io/target: internal.${SECRET_DOMAIN}
hostnames:
- "{{ .Release.Name }}.${SECRET_DOMAIN}"
parentRefs:
- name: internal
namespace: network
sectionName: https
route:
app:
annotations:
external-dns.alpha.kubernetes.io/target: external.${SECRET_DOMAIN}
hostnames:
- "{{ .Release.Name }}.${SECRET_DOMAIN}"
parentRefs:
- name: external
namespace: network
sectionName: https
Two route entries: one with external parentRef and external annotation, one with internal parentRef and internal annotation. Same hostname.
ingress:
tailscale:
enabled: true
className: tailscale
hosts:
- host: "{{ .Release.Name }}"
persistence:
data:
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 5Gi
storageClass: ceph-block
globalMounts:
- path: /data
persistence:
data:
existingClaim: myapp
globalMounts:
- path: /data
readOnlyRootFilesystem: true)persistence:
tmp:
type: emptyDir
globalMounts:
- path: /tmp
defaultPodOptions:
securityContext:
runAsNonRoot: true
runAsUser: 568
runAsGroup: 568
fsGroup: 568
fsGroupChangePolicy: OnRootMismatch
supplementalGroups: [10000]
seccompProfile: { type: RuntimeDefault }
containers:
app:
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: ["ALL"] }
Note: UID/GID 568 is the default. Some images require a different UID — verify against the container's expected user. If the app writes to paths outside mounted volumes, add emptyDir mounts for /tmp, /cache, etc.
# Find latest tag
crane ls <registry>/<image>
# Verify against official releases
gh release list --repo <owner>/<repo> --limit 10
# Get digest
crane digest <registry>/<image>:<tag>
Result:
image:
repository: ghcr.io/org/app
tag: v1.0.0@sha256:abc123...
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: myapp-secret
spec:
secretStoreRef:
kind: ClusterSecretStore
name: onepassword-connect
target:
name: myapp-secret
data:
- secretKey: API_KEY
remoteRef:
key: myapp
property: api_key
Create secret.sops.yaml, encrypt with sops --encrypt --in-place <file>.
After creating the secret (either method), reference it in the HelmRelease container spec:
containers:
app:
envFrom:
- secretRef:
name: myapp-secret
Verification: If you created a secret but the app can't see its values, check that envFrom or env.valueFrom exists in the container spec.
references/deploy-app.md — Full deployment process with verification~/home-ops/docs/ai-context/ARCHITECTURE.md — GitOps architecture~/home-ops/docs/ai-context/CONVENTIONS.md — Full conventions and common mistakes~/home-ops/docs/ai-context/WORKFLOWS.md — Operational workflows~/home-ops/docs/ai-context/NETWORKING.md — Traffic flows, DNS, OIDC| Namespace | Purpose | |-----------|---------| | database | PostgreSQL, MariaDB, Dragonfly, Mosquitto | | downloads | Media acquisition (arr stack, qbittorrent, sabnzbd) | | entertainment | Media serving (plex, immich, audiobookshelf) | | games | Gaming (pelican, romm) | | home | Utilities (homepage, bookstack, paperless, searxng) | | home-automation | IoT (home-assistant, n8n) | | network | Gateways, DNS, cloudflared, tailscale | | observability | Prometheus, grafana, loki, alerting | | plane | Project management | | security | Authentication (pocket-id) | | storage | Backups (kopia, volsync, syncthing, minio) |
type(scope): description
Pair-programmed with Claude Code - https://claude.com/claude-code
Co-Authored-By: Claude <[email protected]>
Co-Authored-By: the user <[email protected]>
Types: feat, fix, chore, docs, refactor
All changes through Git. Flux is the authority.
development
Deeply personal mentor and guide. Use when struggling, wanting to quit, feeling overwhelmed, or doubting yourself. Empathy-first. Build this skill around YOUR psychology.
tools
Build automation workflows with n8n for game dev tasks. Use when automating repetitive processes, setting up notifications, scheduling backups, or connecting services. Reduces manual overhead that ADHD brains find hardest to maintain.
testing
Query and diagnose the home Kubernetes cluster. Use when checking cluster health, troubleshooting pods/services/routes, inspecting storage, or understanding what's deployed. Covers Talos node management, Ceph storage, Cilium networking.
development
Navigate the Steam publishing pipeline from Steamworks registration to post-launch. Use when setting up a store page, preparing builds for upload, planning a release timeline, pricing, marketing, or deciding about Early Access. For a solo dev publishing their first game.