Dependencies and health checks¶
Two features turn a set of Terraform objects into an ordered, verifiable
rollout: spec.dependsOn sequences stacks, spec.healthChecks proves that
what a stack created actually answers.
Dependencies: spec.dependsOn¶
apiVersion: infra.argocd.io/v1alpha1
kind: Terraform
metadata:
name: app-cluster
namespace: team-a
spec:
dependsOn:
- name: network # same namespace unless namespace: is set
- name: shared-dns
namespace: platform # requires --no-cross-namespace-refs=false on the controller
# …
Before each run the controller checks every dependency and waits
(Ready=Unknown / DependencyNotReady, Argo CD Progressing, retried after
spec.retryInterval) until all of them are:
- Ready at their current generation — a dependency whose own plan is waiting for approval, or whose last apply failed, blocks its dependants;
- at the same revision, when both objects use the same source URL and ref — so a commit that touches two stacks is applied in order rather than the dependant running against the dependency's previous revision;
- done writing outputs — if the dependency has
writeOutputsToSecret, that Secret must exist.
Nothing is passed implicitly. To consume a dependency's values, read its
outputs Secret with varsFrom (see Outputs › In another Terraform object).
Deletion protection¶
When A depends on B, the controller adds a finalizer
dependency.infra.argocd.io/A to B. Deleting B first leaves it in
Terminating with Ready=False / DeletionBlockedByDependants until A is
gone, then B's own deletion (and destroy, if configured) proceeds. This
makes kubectl delete -f whole-directory/ and Argo CD prune safe regardless
of order: dependants are always destroyed before their dependencies.
Dependencies vs. sync waves¶
Both order things; they operate at different levels:
spec.dependsOn |
Argo CD sync waves | |
|---|---|---|
| Orders | Terraform objects among themselves, continuously (every reconcile) | Any resources within one Application, once per sync |
| Blocks on | Dependency Ready + same revision + outputs present | Previous wave Healthy |
| Works across Applications | yes (same cluster) | no |
| Deletion | dependants first, enforced by finalizers | reverse wave order during prune |
Use dependsOn between stacks, and sync waves between a stack and the
Kubernetes workloads that consume it. They compose: a Terraform object in
wave 0 that itself dependsOn another object simply stays Progressing — which
holds wave 1 — until its dependency is Ready.
Example: three-tier rollout¶
# wave 0: network (no dependencies)
kind: Terraform
metadata: { name: network, annotations: { argocd.argoproj.io/sync-wave: "0" } }
spec:
writeOutputsToSecret: { name: network-outputs }
---
# wave 0: database, waits for network through dependsOn
kind: Terraform
metadata: { name: database, annotations: { argocd.argoproj.io/sync-wave: "0" } }
spec:
dependsOn: [{ name: network }]
varsFrom: [{ kind: Secret, name: network-outputs, varsKeys: [vpc_id, "private_subnets:subnet_ids"] }]
writeOutputsToSecret: { name: database-outputs, outputs: ["endpoint:DB_HOST", "password:DB_PASSWORD"] }
healthChecks:
- name: db
type: tcp
address: "{{ .DB_HOST }}:5432"
---
# wave 1: the application, only after both Terraform objects are Healthy
kind: Deployment
metadata: { name: api, annotations: { argocd.argoproj.io/sync-wave: "1" } }
spec:
template:
spec:
containers:
- name: api
envFrom: [{ secretRef: { name: database-outputs } }]
Health checks: spec.healthChecks¶
After a successful apply (and after outputs are written) the controller runs
each check from inside the controller Pod. All must pass for the object to
become Ready; a failure sets Ready=False / HealthChecksFailed (Argo CD
Degraded), emits a Warning event with the rendered target, and the run is
retried after spec.retryInterval.
spec:
writeOutputsToSecret:
name: web-outputs # required for templates to see outputs
healthChecks:
- name: lb
type: http
url: "https://{{ .lb_hostname }}/healthz"
timeout: 30s # default 20s
- name: db
type: tcp
address: "{{ .db_host }}:{{ .db_port }}"
| Field | Notes |
|---|---|
type: http + url |
GET; any 2xx/3xx status passes. Follows redirects. No custom headers or TLS options — use a dedicated health endpoint |
type: tcp + address |
host:port; passes when the TCP connection succeeds |
timeout |
Per check, default 20s |
| templates | Go text/template over the outputs Secret keys (post-rename). A missing key is an error (missingkey=error), not an empty string |
Health checks run from the controller, so the target must be reachable from the controller's namespace: a public load balancer, a Service in the cluster, or a private endpoint the cluster can route to. Checks are re-run on the next reconcile only if the previous attempt failed; a Healthy object is not re-checked until something changes.
When to use them¶
- Prove that a managed database, load balancer or cluster is actually accepting connections before Argo CD proceeds to the next sync wave.
- Catch provider "success" that is not really ready yet (DNS propagation, eventual consistency) and keep the object Degraded until it is.
Don't use them as a monitoring system: they run after applies, not continuously. For ongoing checks use your monitoring stack and Drift detection for configuration drift.