GitOps with ArgoCD
git.skypalette.ai/skyadmin/gitops
is the source of truth for every Kubernetes resource that isn’t bootstrap
state. ArgoCD continuously reconciles main into the cluster.
Layout
gitops/
├── apps/ # ArgoCD Application manifests (app-of-apps root)
├── base/ # Reusable per-app manifests
│ ├── web-app/ # Includes deployment, service, ingress, servicemonitor
│ └── …
├── components/ # Kustomize Components — annotate, observability, etc.
├── overlays/ # Environment-specific patches (staging/production)
├── infrastructure/ # Cluster-level: monitoring, ingress-nginx, cert-manager…
└── docs/ # The README of every layerApp-of-apps
The root ArgoCD Application (apps/kustomization.yaml) renders every
other Application — one per third-party stack
(kube-prometheus-stack, loki, alloy, vaultwarden, velero, …) plus one per
homegrown app overlay. Adding a new app means a PR that drops an
Application manifest under apps/templates/ or apps/infrastructure/ and
its base/overlay; ArgoCD picks it up on the next sync.
SOPS + KSOPS
Secrets are committed encrypted. An age recipient is hard-coded in
.sops.yaml; the corresponding key lives only in the operator’s
password manager and in the running ArgoCD pod (mounted from a manually-
applied Secret bootstrapped once).
The ArgoCD repo-server runs with the KSOPS plugin enabled, so during
manifest render it decrypts any *.sops.yaml it encounters and emits the
plaintext to the in-memory render. The plaintext never lands on disk or in
ArgoCD’s UI.
# .sops.yaml
creation_rules:
- encrypted_regex: ^(data|stringData)$
age:
- age16tffvlf4cd79utck57ypm68xvy2y5v6ruzlum6hxf5593s5vsctqle5zhqEditing a secret is sops infrastructure/monitoring/alertmanager-ntfy/url-secret.sops.yaml;
SOPS opens an editor on the decrypted view, re-encrypts on save.
Image Updater
argocd-image-updater polls the Forgejo registry for new tags matching a
regex per Application. When it finds a new tag it commits the bump back to
the gitops repo (via Forgejo basic-auth using a scoped token), which Argo
then picks up. The whole loop runs without manual helm upgrade or
kubectl set image.
Sync policy
Every Application has automated: { selfHeal: true, prune: true } with
two consequences:
- Drift is fixed automatically. If someone
kubectl edits a live resource that Argo owns, Argo reverts within seconds. - Removed resources are deleted. Comment a resource out in Git and
Argo deletes the live object. The exception: namespaces have
prune: falseso accidental file deletion can’t wipe out everything in a namespace.
Verifying state
kubectl -n argocd get applications
kubectl -n argocd get application kube-prometheus-stack \
-o jsonpath='sync={.status.sync.status} health={.status.health.status}\n'For a one-pass-fits-all check including PVC capacity, scrape targets, and
custom alerts, see scripts/validate-observability.sh
covered in Observability.