Skip to content

GitOps & the app-of-apps

Project Loom runs on one idea: Git is the desired state, and a controller continuously makes the cluster match it. You almost never kubectl apply by hand — you commit YAML, push, and Argo CD reconciles. (The one exception is the one-time root-app bootstrap — see App-of-apps below.) This is GitOps, and it applies the same way to every app on the platform.

flowchart LR
  Dev([You]) -->|git push| Repo[(webb1es/loom)]
  Repo -->|polls / webhook| Argo[Argo CD]
  Argo -->|diff desired vs live| K8s[(Kubernetes)]
  K8s -->|actual state| Argo
  Argo -->|apply changes| K8s
  Argo -.->|out of sync? self-heal| K8s

Argo CD compares the manifests in Git against what’s live in the cluster. If they differ, it applies the difference (and with selfHeal, it reverts any manual drift).

A single root Application points at the apps/ directory. Platform files there are Argo CD Applications (operators, Infisical). apps/workloads.yaml is an ApplicationSet that generates one Application per app×env (penvoice-staging, penvoice-prod, …). Bootstrap the platform by applying one manifest; everything else cascades.

flowchart TD
  Root[root Application] --> A[apps/cnpg-operator]
  Root --> B[apps/keycloak-operator]
  Root --> C[apps/workloads ApplicationSet]
  C --> C1[penvoice-staging / penvoice-prod]
  C --> C2[keycloak-staging / keycloak-prod]
  A --> A1[CloudNativePG operator]
  C1 --> C1b[API Rollout + Postgres + Redis + ingress]
  C2 --> C2b[Keycloak instance + Postgres]

Why it matters: adding a new app is a workloads/<app>/{base,overlays} copy plus two list entries in apps/workloads.yaml. Standing up a node is Provision (Helm, then the root app). See Onboard an app for the next product.