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.
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 <noreply@anthropic.com>
Co-Authored-By: the user <your-email@example.com>
Types: feat, fix, chore, docs, refactor
All changes through Git. Flux is the authority.
npx skills add gavinmcfall/home-ops-deployer下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer