GitOps pattern¶
Every workload deployment in the cluster follows the same mechanism, in
three layers. There's no manual kubectl apply, no deploy step inside a CI
pipeline — a git push to the main branch of a gitops.* repo is the
entire deploy mechanism.
flowchart TB
subgraph tier1["1 · Discovery (once, at bootstrap)"]
appset["ApplicationSet/gitops-repos<br/>(argocd namespace)"]
end
subgraph tier2["2 · Per repository"]
repo["gitops.<app> repo"]
argocdyaml["argocd/*.yaml<br/>(Application, project: homelab)"]
end
subgraph tier3["3 · Payload"]
helm["helm/<name>/<br/>or kustomize/<name>/"]
end
subgraph cluster["Cluster"]
workload["Deployment, Service,<br/>HTTPRoute, ExternalSecret..."]
end
appset -- "SCM provider generator<br/>regex ^gitops\\..*" --> repo
appset -- "generates one Application<br/>per repo found" --> argocdyaml
argocdyaml -- "source.path" --> helm
helm -- "sync (prune + selfHeal)" --> workload
Layer 1 — Automatic discovery¶
The ApplicationSet/gitops-repos, installed once at cluster bootstrap (see
Cluster bootstrap), uses an scmProvider generator
against the cmoreira-dev GitHub org, filtering repositories matching
^gitops\..*, re-polling every 300s.
For every repository found, the ApplicationSet automatically creates an
Application (named after the repo) pointed at the argocd/ folder
inside that repo, with CreateNamespace=true and automated sync
(prune + selfHeal).
Layer 2 — Per-repository manifests¶
Inside each gitops.<app> repo, the argocd/ folder holds one or more
Application manifests, using project: homelab (the AppProject created
at bootstrap, with broad permissions over the org and the cluster) and
pointing back at a folder within the same repository — helm/<name> or
kustomize/<name>.
Most repos have an essentially empty argocd/ (just .gitkeep), because
the Application generated by Layer 1 already covers one workload per repo.
argocd/ is only populated manually when a repo delivers more than one
component — for example gitops.teupadel.com and gitops.local-sara
declare two Applications (<app>-api and <app>-ui), each with its own
helm/api/helm/ui.
Layer 3 — Payload¶
The content actually applied to the cluster: a Helm chart (usually a thin
wrapper chart with dependencies: on an upstream chart, or on the
generic app chart) or plain Kustomize
manifests. sync-wave annotations sequence installs that depend on each
other (e.g. CRDs/operator before the resources that use them).
Full sequence, from push to running workload¶
sequenceDiagram
participant Dev as git push (gitops.<app>)
participant AS as ApplicationSet
participant App as Application (argocd/*.yaml)
participant Chart as helm|kustomize
participant K8s as Cluster
Dev->>AS: new commit on main
AS-->>AS: Application for this repo already exists (created once)
App->>Chart: reads source.path
Chart->>K8s: sync (ServerSideApply, CreateNamespace)
K8s-->>App: selfHeal reverts any manual drift
Dependency automation¶
Every gitops.* repo with a helm/ folder has a renovate.json
(helmv3 manager, scoped to helm/**) — Renovate automatically opens PRs
when a chart dependency has a new version. Merging the PR is what actually
promotes the update, since the merge to main is itself the deploy trigger.