Conventions for contributing to homelab's Helm charts, helmfiles, Kubernetes manifests, and shell scripts. These are repo-local patterns — not Go/Rust/general standards from companion repos (core CLI, kmsvc).
The helmfile is a **Go template**, not a shell script. Use Go template syntax for environment variable interpolation, not shell syntax.
**Correct:**
```yaml
set:
- name:adminPassword
value:{{env "GRAFANA_ADMIN_PASSWORD" }}
```
**Incorrect:**
```yaml
set:
- name:adminPassword
value:${GRAFANA_ADMIN_PASSWORD} # Shell syntax — not expanded by helmfile
```
**Organization:**
- Use logical comment headers to separate sections: `# ── cert-manager ──`.
- Group related releases together.
- Use `needs:` for dependency ordering (release A waits for release B before deploying).
- Example (from helmfile.yaml.gotmpl, lines 439–449):
```yaml
- name: loki
namespace: logging
needs:
- storage/minio
- name: promtail
namespace: logging
needs:
- logging/loki
```
- **Namespace declaration:** Specify namespace at the release block level, not helmfile-level default.
```yaml
- name: prometheus
namespace: monitoring
createNamespace: true
```
### Values Files Pattern
- **Never hardcode secrets** in values.yaml or ConfigMap keys.
- Store secrets in Vault via `core put cluster/KEY KEY="value"`.
- Reference at deploy time using `{{ env "VAR" }}` in helmfile.
- **External values files:** Always use `values:` block pointing to `.yaml` files, not inline YAML.
```yaml
- name: minio
values:
- k8s/storage/minio-values.yaml
```
- **Environment-specific overrides:** For complex releases (e.g., SQS), use `environments/` subdirectory with `helmfile.yaml.gotmpl` at the service level.
## Infrastructure as Code (IaC) — Single Source of Truth
**Core principle:** All infrastructure state must be declaratively managed via Terraform or Helm (via helmfile + Terraform). No ad-hoc scripts, manual kubectl, or side-by-side resource definitions.
**Rule: Field name in Secret = environment variable name in Vault.**
When storing a secret via `core put cluster/KEY KEY="value"`, the field name and Vault variable name must match. This ensures helmfile's `{{ env "VAR" }}` expansion works correctly.
Example (from CLAUDE.md gotcha "Field name = variable name"):
```bash
# Correct
core put cluster/MINIO_ROOT_PASSWORD MINIO_ROOT_PASSWORD="value"
core put cluster/GRAFANA_ADMIN_PASSWORD GRAFANA_ADMIN_PASSWORD="value"
# Incorrect (won't expand in helmfile)
core put cluster/MINIO_SECRET value="value" # Field name ≠ variable name
```
Reference in helmfile (helmfile.yaml.gotmpl, lines 400–401):
```yaml
set:
- name: rootPassword
value: {{ env "MINIO_ROOT_PASSWORD" }}
```
### Never Use `--env` Flags in Manifests
Avoid kubectl flags like `--env KEY=value` in manifests or deployment specs. This exposes secrets in `kubectl describe` output.
**Correct:** Use Secret volumes (k8s/storage/minio-values.yaml, lines 40–42):
```yaml
envFrom:
- secretRef:
name: minio-oidc # Reference a Secret, don't expose in manifest
```
**Incorrect:**
```yaml
env:
- name: MINIO_OIDC_SECRET
value: "sensitive-value" # Visible in kubectl describe
```
### Namespace-First Organization
Organize manifests by namespace: `k8s/<namespace>/`
Structure per namespace:
```
k8s/storage/
├── minio-values.yaml
├── minio-bucket-init.sh
└── (local charts if any)
k8s/monitoring/
├── prometheus-values.yaml
├── dashboards/ # ConfigMap files auto-loaded via helmfile postsync hook
Dashboards are stored as JSON ConfigMaps in `k8s/monitoring/dashboards/` and auto-loaded via helmfile postsync hook (see helmfile.yaml.gotmpl, lines 471–477).
- [ ] **ServiceMonitor:** Created and auto-scraped by Prometheus operator.
- [ ] **PrometheusRule:** Alert rules defined for errors, latency, SLO violations.
- [ ] **Grafana dashboard:** 6-row template auto-loaded via ConfigMap.
- [ ] **Ingress:** Rule added if external access needed (see `k8s/ingress/ingress.yaml`).
- [ ] **OIDC integration:** If UI component, integrated with Authentik (see `core iam bootstrap`).
---
## Shared/Reusable Repos & Image Publication
### When to Publish as Public GHCR
**Rule:** If a service's chart + image source lives in a separate repo (not in homelab), it **must** be published as a **public GitHub repo** under the `Riotpiaole` org.
**Rationale:**
- Local in-cluster charts are fine for infra-owned services.
- Shared/reusable service charts should be version-pinned and publicly available for:
- Reuse across different clusters (other labs, staging, prod).
- Consumption by Argo CD apps (CI/CD pipeline).
- Independent evolution without tight coupling to homelab repo.
### Workflow
**Develop locally:**
1. Create companion repo (e.g., `kafaka-management-service`, `queue-operator`) in a private or public repo.
2. Include Dockerfile and Helm chart.
**Build & publish:**
1. Set up CI/CD in the companion repo (GitHub Actions).
2. Build image and push to GHCR: `ghcr.io/Riotpiaole/<repo>:<tag>`.
3. Tag release and publish chart (npm registry, GitHub releases, or OCI registry).
**Reference in homelab:**
1. Pin image tag + chart version in helmfile.yaml.gotmpl.
2. Chart `repositories` block references the public Helm repo (or OCI registry).