Argo CD Entrypoints¶
The review map for everything directly rendered by the root Application from infrastructure/controllers/argocd/apps/.
root.yaml is the manual seed and is not self-managed. Once it exists, Argo CD manages this entrypoint tree, including file moves inside apps/, ApplicationSet edits, and AppProject edits.
Layout¶
| Folder | Purpose |
|---|---|
bootstrap/ |
Wave 0 foundation apps required before the rest of GitOps can converge |
core-dependencies/ |
Storage, restore, and bootstrap services that later apps depend on |
custom-entrypoints/ |
Intentional standalone apps kept out of AppSets for ordering or render behavior |
appsets/ |
Broad directory discovery for infrastructure, databases, monitoring, and user apps |
Entrypoint Table¶
| Entrypoint | Type | Wave | Reason | Can move to AppSet? |
|---|---|---|---|---|
projects.yaml |
AppProjects | 0 | Project grouping and homelab trust boundary | No, foundational Argo CD config |
bootstrap/argocd.yaml |
Application | 0 | Self-manages the Argo CD Helm chart and values | No, self-management entrypoint |
bootstrap/cilium-app.yaml |
Application | 0 | CNI and Gateway API foundation | No, must be healthy before pods and routes |
bootstrap/1passwordconnect.yaml |
Application | 0 | Secret backend for External Secrets | No, secret dependency for later waves |
bootstrap/external-secrets.yaml |
Application | 0 | ExternalSecret CRDs and controller | No, CRDs are required by downstream apps |
core-dependencies/cert-manager-app.yaml |
Application | 1 | Certificate controller required before webhook-cert consumers in later waves | No, required before cert-dependent apps |
core-dependencies/longhorn-app.yaml |
Application | 1 | Storage foundation before PVC consumers | No, required before restore/app PVC flows |
core-dependencies/snapshot-controller-app.yaml |
Application | 1 | VolumeSnapshot CRDs and controller | No, required by backup/restore flows |
core-dependencies/kopiur-operator-app.yaml |
Application | 2 | kopiur operator (Kopia-native backup): CRDs + controller + webhook + volume populator; no monitoring dependency | No, required before managed app PVCs |
core-dependencies/kopiur-config-app.yaml |
Application | 3 | kopiur repo config: ClusterRepository cluster-kopia + credential fanout + VolumeSnapshotClass longhorn-snapclass |
No, required before managed app PVCs |
custom-entrypoints/keda-app.yaml |
Application | 4 | Standalone to isolate its render from the AppSet generator | Maybe, if AppSet render stays stable |
custom-entrypoints/vertical-pod-autoscaler-app.yaml |
Application | 4 | VPA controller (recommender/updater/admission) | Maybe, if AppSet render stays stable |
custom-entrypoints/vertical-pod-autoscaler-observability-app.yaml |
Application | 6 | Optional VPA PodMonitor and alerts after monitoring CRDs exist | No, keeps observability out of core |
custom-entrypoints/temporal-worker-controller-app.yaml |
Application | 4 | Standalone to isolate its render from the AppSet generator | Maybe, if AppSet render stays stable |
custom-entrypoints/strimzi-app.yaml |
Application | 4 | Kafka CRDs/operator must precede wave-6 Kafka resources; standalone destination kafka also avoids the infrastructure AppSet's basename→namespace assumption (strimzi would be wrong) |
No, both ordering and destination namespace are exceptional |
custom-entrypoints/opentelemetry-operator-app.yaml |
Application | 5 | Core operator after cert-manager; ServiceMonitor kept out of core | Maybe, if cert-manager dependency is otherwise enforced |
custom-entrypoints/keda-observability-app.yaml |
Application | 6 | Optional KEDA ServiceMonitor resources after monitoring CRDs exist | No, keeps observability out of core |
custom-entrypoints/vpa-system-policies-app.yaml |
Application | 6 | VPA policies for the small set of bootstrap/system workloads without a co-located owner | No, ownership exception is explicit |
custom-entrypoints/opentelemetry-operator-observability-app.yaml |
Application | 6 | Optional OpenTelemetry ServiceMonitor after monitoring CRDs exist | No, keeps observability out of core |
appsets/infrastructure-appset.yaml |
ApplicationSet | 4 | Explicit list of core infrastructure directories | N/A |
appsets/database-appset.yaml |
ApplicationSet | 4 | Discovers infrastructure/database/*/* (Redis + shared DB support); fully automated since the CNPG retirement (2026-08-13) |
N/A |
appsets/monitoring-appset.yaml |
ApplicationSet | 5 | Discovers monitoring/* after core infra |
N/A |
appsets/my-apps-appset.yaml |
ApplicationSet | 6 | Discovers my-apps/*/* (excluding Components); all automated + self-healing |
N/A |
Generated Application identities are domain-prefixed:
infrastructure-<component>, database-<database>, monitoring-<component>,
and my-apps-<app>. Namespaces still use the leaf directory name. The prefix
prevents operationally dangerous UI ambiguity such as confusing the Temporal
database Application with the Temporal server/schema Application.
ApplicationSet waves order creation of the generator objects, not the health of their generated Applications. Those children reconcile independently. See wave scope before treating an AppSet wave as a dependency barrier.
Bootstrap guardrail — observability is not a core dependency¶
A bootstrap-critical app must render successfully without monitoring.coreos.com CRDs. Argo's Helm chart conditionally renders its monitors only when the installed API capability exists. Other
monitoring resources belong in optional observability overlays. Those CRDs do not exist until
kube-prometheus-stack (Wave 5); an earlier-wave app shipping them fails dry-run and deadlocks the
App-of-Apps wave gate. Put observability CRs in a separate optional app that syncs after Wave 5
(e.g. keda-observability at Wave 6, split out of KEDA's Wave-4 core). Do not install Prometheus
Operator CRDs early — SkipDryRunOnMissingResource is an escape hatch / observability-app option only,
never a core fix. cert-manager is at Wave 1 (not 4) so early cert-dependent apps can start
(historically the Wave-3 cnpg-barman-plugin; the deadlock lesson stands even though that app is
retired). Full detail: cluster DR nuke restore runbook.
Bootstrap guardrail — Application health does not prove webhook reachability¶
Waves gate on Argo CD Application health. For a webhook Deployment that means
ready replicas == desired — and kubelet decides readiness by probing the pod
locally, on its own node. Neither signal involves the API server. A webhook
pod can therefore be Running, Ready, and its Application Healthy, while the
API server cannot reach it across the pod network. Every later wave then proceeds
into a webhook that cannot be called.
The blast radius is set by failurePolicy, not by the size of the component. A
cluster-scoped failurePolicy: Fail webhook rejects every matching write in
the cluster while unreachable, and a sync can fail after applying only part of its resources. Unrelated apps
can stall waiting for Secrets or other objects rejected by admission.
Two rules follow:
- Fail-closed cluster-scoped webhooks are pinned to the control plane
(
nodeSelector: node-role.kubernetes.io/control-planeplus the matchingNoScheduletoleration). The API server calls them node-locally, so no pod-network fault on any other node can break admission. This applies tocert-manager. Availability is then coupled to the API server's own availability, which is the correct coupling: if the control plane is down, admission is moot. - Do not add a custom Lua health check to probe webhook reachability. Argo CD
configuration stays simple; the reachability signal belongs in alerting
(
monitoring/prometheus-stack/admission-webhook-alerts.yaml, which reads the API server's ownapiserver_admission_webhook_*metrics), not in the wave gate.
Notes¶
project-nomadis intentionally managed byappsets/my-apps-appset.yamlas a single bundled app atmy-apps/home/project-nomad. Its child folders are resources inside that app, not generated Argo CD Applications.my-apps/common/*is excluded from the my-apps generator: those directories are shared Kustomize Components (kind: Component), which kustomize builds as an empty render — without the exclude the AppSet generates a phantom zero-resource Application.validate-argocd-apps.shCheck 8 fails CI if a Component dir ever becomes discoverable again.- All four ApplicationSets use strict Go templates (
missingkey=error). Git directory fields are objects in Go-template mode. The leading.means "the current generator result";pathis the Git generator's directory object, and the remaining names select fields from it. Therefore use{{ .path.path }}/{{ .path.basename }}rather than the deprecated fasttemplate forms{{ path }}/{{ path.basename }}. Strict missing-key handling deliberately fails generation instead of silently producing an Application with an empty name, source path, or namespace. The render CI also rejects any non-Component kustomization that produces zero Kubernetes objects. - Changing a generated Application name replaces the Application object. Never merge an identity rename into a running cluster without a staged finalizer migration. The 2026-07-31 prefix migration is complete; its historical merge-after-destruction step is not part of a normal rebuild.
rustfs-lifecycleis not a standalone Argo entrypoint. It is generated byappsets/infrastructure-appset.yamlfrominfrastructure/storage/rustfs-lifecycleat wave 4.- Global ignore rules for HTTPRoute, ExternalSecret, and PVC restore fields live in
infrastructure/controllers/argocd/values.yaml; AppSets should only carry app-specific ignore rules. - AppProjects are intentionally permissive for a single-operator homelab. They are labels and UI grouping, not hard tenant guardrails.
- Hook Jobs are acceptable when they are idempotent, bounded, and documented. The RustFS lifecycle Job is a PostSync hook because S3 lifecycle PUTs are idempotent and Kubernetes Jobs are immutable.
Project Nomad App Boundary¶
Project Nomad is not special to Argo CD; it is special only in repo shape. The my-apps ApplicationSet discovers app directories with my-apps/*/*, so my-apps/home/project-nomad is the generated Application boundary.
Inside that directory there is one parent kustomization.yaml. Subdirectories such as mysql/, redis/, qdrant/, embeddings/, kiwix/, protomaps/, cyberchef/, and flatnotes/ are resource folders referenced by the parent kustomization, not independent app directories.
Do not exclude my-apps/home/project-nomad/*; that pattern targets child folders the AppSet does not generate. If Project Nomad should ever become multiple Argo CD Applications, add child kustomization.yaml files deliberately and update the generator/validation model at the same time.