Phase 6: Declarative GitOps Bootstrapping & In-Git Secret Encryption
| Milestone Attribute | Engineering Specification |
|---|---|
| Phase Scope | Continuous Delivery Control Loop & Asymmetric Secret Encryption |
| Target Infrastructure | K3s Kubernetes Cluster (k3s-prod), Flux CD v2, Mozilla SOPS |
| Primary Code Paths | kubernetes/bootstrap/, kubernetes/platform/, .sops.yaml |
| Relevant Decisions | ADR-007, ADR-014 |
| Operational Status | Production Verified (Platform v3.0.0) |
1. Executive Summary & Objective
Phase 6 institutes enterprise-grade GitOps Continuous Delivery and Cryptographic In-Git Secrets Management on k3s-prod.
At the completion of this phase:
1. Flux CD v2 functions as the cluster's autonomous control loop, continuously pulling and reconciling manifests directly from the GitHub repository (homelab-ops).
2. Configuration drift is completely eliminated: manual kubectl apply interventions are superseded by Git commits.
3. Secrets (database credentials, API tokens, webhook keys) are encrypted in Git via Mozilla SOPS using an Age keypair, decrypted dynamically in-memory inside K3s.
4. The entire GitOps plane consumes only ~120MB RAM, preserving maximum memory for production applications.
2. Engineering Rationale: Lean Tooling Selection
A. Flux CD v2 vs. ArgoCD (Memory Economics)
While ArgoCD provides an interactive web UI, running its full suite (server, dex, repo-server, application-controller, Redis) consumes ~700MB–1.0GB of RAM.
On our 16GB Mini PC running 9 production services:
- Flux CD v2 uses only ~120MB–150MB RAM total across its controllers (
source-controller,kustomize-controller,helm-controller). - Flux integrates natively with Mozilla SOPS out-of-the-box via Kustomize decryption providers, eliminating third-party sidecars.
- Operating as a pure Kubernetes controller without an exposed web dashboard reduces the cluster attack surface.
B. Mozilla SOPS + Age vs. HashiCorp Vault
- HashiCorp Vault is heavy over-engineering for a single-node platform, consuming >1GB RAM, requiring complex unseal ceremonies, and introducing certificate renewal loops.
- Mozilla SOPS with Age (
age-keygen) encrypts only values in YAML manifests, leaving keys, names, and labels in plain text. This allows clean, readable GitHub Pull Request diffs while guaranteeing that no sensitive credentials leak into Git history.
3. GitOps & Secret Lifecycle Pipeline
Reconciliation Architecture:
[ GitHub Repository (origin/main) ]
├── .sops.yaml (Age Public Key)
├── kubernetes/bootstrap/ (Flux Controllers)
├── kubernetes/platform/ (Infra Overlays)
└── kubernetes/apps/ (Encrypted secrets: *.enc.yaml)
│
│ Git Pull Every 10m (or Webhook Trigger)
▼
[ Flux CD v2 Engine (flux-system: ~120MB RAM) ]
│ Reads Secret: sops-age (Age Private Key)
│ Decrypts secrets in-memory (RAM only)
▼
[ Native Kubernetes Secrets & Pods ]

4. Technical Execution Details
1. Generating Master Age Keypair
Generated a dedicated Age keypair on the administrative workstation:
mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
# Extract public key (age1...)
grep "public key:" ~/.config/sops/age/keys.txt
2. Configuring Repository Encryption Rules
Defined .sops.yaml at the repository root:
creation_rules:
- path_regex: kubernetes/.*\.enc\.yaml$
encrypted_regex: "^(data|stringData)$"
age: "<AGE_PUBLIC_KEY>"
3. Injecting Age Private Key into Cluster
Injected the private key into the flux-system namespace to enable in-memory decryption:
cat ~/.config/sops/age/keys.txt | kubectl -n flux-system create secret generic sops-age \
--from-file=age.agekey=/dev/stdin \
--dry-run=client -o yaml | kubectl apply -f -
4. Bootstrapping Flux CD v2 Controller
flux bootstrap github \
--owner=vsingh55 \
--repository=homelab-ops \
--branch=main \
--path=kubernetes/bootstrap \
--personal
5. Configuring SOPS Decryption in Kustomization
Configured the root Kustomization in kubernetes/bootstrap/ to decrypt SOPS secrets automatically:
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: apps
namespace: flux-system
spec:
interval: 10m0s
path: ./kubernetes/apps
prune: true
sourceRef:
kind: GitRepository
name: flux-system
decryption:
provider: sops
secretRef:
name: sops-age
5. Verification & Quality Assertions
1. Flux Controller Health Audit
flux check
# Output: All checks passed across source-controller, kustomize-controller, and helm-controller.
2. Kustomization Reconciliation Status
flux get kustomizations
# Output:
# NAME REVISION SUSPENDED READY MESSAGE
# flux-system main@sha1:2fa158b False True Applied revision: main@sha1:2fa158b
# apps main@sha1:2fa158b False True Applied revision: main@sha1:2fa158b
3. In-Memory Decryption Verification
kubectl get secret -n database postgres-credentials -o jsonpath='{.data.password}' | base64 -d
# Output: Confirms plain-text secret is mounted inside Kubernetes while remaining 100% encrypted in Git.
6. Exit Gate & Phase Transition
With declarative GitOps active and in-git secrets cryptographically protected, the platform advanced to Phase 7: Production Application Fleet Deployment & Storage Tiering.