ArgoCD GitOps: Deep Dive
This document explains how ArgoCD implements GitOps, how the Application CRD works field by field, and how every piece in this demo fits together. It is meant to be read alongside the YAML files in this repo. If you want step-by-step setup instructions, see the README instead.
1. GitOps Principles
Section titled “1. GitOps Principles”GitOps is a set of practices where Git is the single source of truth for your infrastructure and application configuration. Four principles define it.
Git as the single source of truth. Every desired state, from Kubernetes manifests to Helm values, lives in a Git repository. The cluster should reflect what Git says. If it does not, something is wrong.
Declarative configuration. You describe what you want, not how to get there. Kubernetes already works this way with YAML manifests. GitOps extends that pattern to the delivery pipeline itself.
Automated reconciliation. A controller watches Git and the cluster continuously. When they diverge, it takes action. No human needs to run kubectl apply. No CI pipeline needs cluster credentials.
Observable state. You can always tell whether the cluster matches Git. ArgoCD surfaces this as sync status: “Synced” means they match, “OutOfSync” means they do not. This makes drift immediately visible.
The practical effect: your Git history becomes your deployment history. Want to roll back? Revert a commit. Want to know who changed what? Check git log. Want to audit production? Compare the cluster state against the HEAD of your main branch.
2. ArgoCD Architecture
Section titled “2. ArgoCD Architecture”ArgoCD runs inside your Kubernetes cluster as a set of controllers. Three components do the heavy lifting.
Repo Server
Section titled “Repo Server”The repo server clones Git repositories and generates Kubernetes manifests from them. It understands plain YAML, Helm charts, Kustomize overlays, and Jsonnet. When ArgoCD needs to know what resources should exist, it asks the repo server to render the manifests from a given repo, revision, and path.
In this demo, the repo server clones https://github.com/savitojs/k8s-learn-by-doing.git and renders manifests from paths like demos/simple-app/manifests (plain YAML), demos/helm/chart (Helm), and demos/kustomize/overlays/development (Kustomize).
Application Controller
Section titled “Application Controller”The application controller is the reconciliation loop. It compares the desired state (manifests from the repo server) against the live state (resources in the cluster). When they differ, it either reports “OutOfSync” or, if auto-sync is enabled, applies the changes.
This controller also handles self-healing. If someone manually edits a resource in the cluster, the controller detects the drift and reverts it to match Git.
API Server
Section titled “API Server”The API server exposes the ArgoCD REST and gRPC APIs. The web UI and the argocd CLI both talk to it. It handles authentication, RBAC, and provides the interface for viewing application status, triggering manual syncs, and managing projects.
How a Sync Works
Section titled “How a Sync Works”- The application controller detects that an Application is OutOfSync (either by polling or webhook notification).
- It asks the repo server to render the desired manifests for the configured repo, revision, and path.
- It diffs the desired manifests against the live cluster state.
- It applies the diff to the cluster, creating, updating, or deleting resources as needed.
- It watches the rollout until resources reach a healthy state (Pods running, Deployments available, etc.).
- It updates the Application status to “Synced” and “Healthy”.
By default, ArgoCD polls Git every 3 minutes. You can also configure webhooks for near-instant sync.
3. The Application CRD, Field by Field
Section titled “3. The Application CRD, Field by Field”The Application custom resource is the core of ArgoCD. It tells ArgoCD: watch this Git path and deploy those manifests to that cluster and namespace.
Here is the simple-app Application from this demo, annotated:
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: simple-app namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io/foregroundspec: project: default source: repoURL: https://github.com/savitojs/k8s-learn-by-doing.git targetRevision: HEAD path: demos/simple-app/manifests destination: server: https://kubernetes.default.svc namespace: simple-app syncPolicy: automated: prune: true selfHeal: true allowEmpty: false syncOptions: - Validate=true - CreateNamespace=true - PrunePropagationPolicy=foreground - PruneLast=truemetadata
Section titled “metadata”- name: The application’s identity in ArgoCD. Shows up in the UI and CLI.
- namespace: Must be
argocd. ArgoCD only watches for Application resources in its own namespace. - finalizers: Covered in detail in section 5. Controls what happens when you delete this Application.
spec.project
Section titled “spec.project”Which ArgoCD Project this application belongs to. Projects define RBAC boundaries: which repos are allowed, which namespaces can be targeted, and which resource types can be created. The default project has no restrictions. See section 7 for the custom development project in this demo.
spec.source
Section titled “spec.source”Where ArgoCD should pull manifests from.
- repoURL: The Git repository URL. Must be registered in ArgoCD’s repo configuration (done in
terraform/values.yamlin this demo). - targetRevision: The Git ref to track.
HEADmeans the tip of the default branch. You could also use a tag (v1.2.3), a branch name (release/2.0), or a specific commit SHA. - path: The directory within the repo containing the manifests. ArgoCD auto-detects the format: if it finds a
Chart.yaml, it treats it as Helm. If it finds akustomization.yaml, it treats it as Kustomize. Otherwise, it treats all.yaml/.jsonfiles as plain manifests.
For Helm sources, you can add a helm block with valueFiles, values, or parameters. For Kustomize sources, you can add a kustomize block with namePrefix, images, etc. Neither is needed in this demo because the defaults work.
spec.destination
Section titled “spec.destination”Where to deploy the rendered manifests.
- server: The Kubernetes API server URL.
https://kubernetes.default.svcmeans “the same cluster ArgoCD is running in.” For multi-cluster setups, you would register external clusters and use their server URLs here. - namespace: The target namespace. Combined with
CreateNamespace=truein syncOptions, ArgoCD creates this namespace if it does not exist.
spec.syncPolicy
Section titled “spec.syncPolicy”Controls how and when ArgoCD syncs. This is where the automation lives. Covered in full detail in the next section.
4. Sync Policies Deep Dive
Section titled “4. Sync Policies Deep Dive”The syncPolicy block is what turns ArgoCD from a dashboard into an automated deployment tool.
automated
Section titled “automated”Without this block, ArgoCD only reports drift. You have to click “Sync” in the UI or run argocd app sync to apply changes. Adding the automated block enables continuous deployment.
syncPolicy: automated: prune: true selfHeal: true allowEmpty: falseauto-sync (automated block present)
Section titled “auto-sync (automated block present)”When a new commit lands in Git, ArgoCD automatically applies the changes to the cluster. No human approval, no manual trigger. The controller detects the diff and syncs immediately (within the polling interval or via webhook).
This is enabled for every application in this demo. The moment you push a commit that changes a manifest, ArgoCD picks it up and deploys it.
selfHeal
Section titled “selfHeal”When selfHeal: true, ArgoCD watches for live drift, not just Git drift. If someone runs kubectl edit or kubectl scale to change a resource directly, ArgoCD detects the discrepancy and reverts the change to match Git.
This is powerful for enforcing GitOps discipline. It means the cluster cannot drift from Git, even if someone bypasses the Git workflow. It also means you cannot “hotfix” the cluster manually. Any manual change gets wiped. That is the point.
Default polling for self-heal is every 5 seconds.
prune (auto-prune)
Section titled “prune (auto-prune)”When prune: true, ArgoCD deletes resources from the cluster that no longer exist in Git. Without this, removing a manifest from Git leaves the corresponding resource orphaned in the cluster.
Example: you delete demos/simple-app/manifests/service.yaml from Git. With prune enabled, ArgoCD deletes the Service from the cluster on the next sync. Without prune, the Service stays running with no manifest backing it.
allowEmpty
Section titled “allowEmpty”When allowEmpty: false, ArgoCD refuses to sync if the source path yields zero manifests. This is a safety net. If your repo structure breaks or a path goes empty by accident, ArgoCD will not nuke all the resources in the target namespace.
syncOptions
Section titled “syncOptions”These are flags that modify sync behavior:
- Validate=true: Run server-side validation before applying. Catches schema errors before they hit the cluster.
- CreateNamespace=true: Create the target namespace if it does not exist. Without this, syncing to a non-existent namespace fails.
- PrunePropagationPolicy=foreground: When deleting resources, use foreground cascading deletion. This means the parent resource waits for all dependents to be deleted before it is removed. Prevents orphaned child resources.
- PruneLast=true: Delete resources only after all other sync operations succeed. This prevents a situation where ArgoCD deletes the old resources but fails to create the new ones, leaving you with nothing.
- Cascade=true (on the app-of-apps): Enable cascading deletion of child resources when the parent resource is deleted.
5. Finalizers and Why They Matter
Section titled “5. Finalizers and Why They Matter”Every Application in this demo includes this finalizer:
metadata: finalizers: - resources-finalizer.argocd.argoproj.io/foregroundWhat finalizers do
Section titled “What finalizers do”Kubernetes finalizers prevent an object from being deleted until a controller has finished its cleanup work. When you kubectl delete an Application that has a finalizer, Kubernetes marks it for deletion but does not actually remove it. ArgoCD’s controller sees this, deletes all the managed resources from the cluster, and then removes the finalizer. Only then does Kubernetes delete the Application object itself.
Why this matters for the App-of-Apps pattern
Section titled “Why this matters for the App-of-Apps pattern”Without the finalizer, deleting the app-of-apps Application would remove only the Application resource itself. The child applications (simple-app, helm-app, kustomize-dev, kustomize-prod) would keep running, and all the resources they deployed would remain in the cluster.
With the finalizer, deleting app-of-apps triggers a cascade:
- ArgoCD deletes the child Application resources (because they are managed resources of the parent).
- Each child Application’s finalizer fires, causing ArgoCD to delete the resources those applications manage (Deployments, Services, etc.).
- Once all managed resources are gone, the finalizers are removed, and the Application objects are deleted.
The /foreground variant
Section titled “The /foreground variant”The resources-finalizer.argocd.argoproj.io/foreground variant uses foreground cascading deletion. ArgoCD waits for child resources to be fully deleted before marking the parent as deleted. The alternative, resources-finalizer.argocd.argoproj.io (without /foreground), uses background deletion, which is faster but does not guarantee ordering.
What happens without finalizers
Section titled “What happens without finalizers”If you remove the finalizer and delete an Application, ArgoCD removes the Application object but leaves all managed resources running in the cluster. This is sometimes intentional (you want to stop managing an app without destroying it), but usually it is a mistake.
6. The App-of-Apps Pattern
Section titled “6. The App-of-Apps Pattern”What it is
Section titled “What it is”The App-of-Apps pattern is an ArgoCD Application whose source path contains other Application manifests. Instead of managing Deployments and Services, it manages Application resources.
app-of-apps (Application) | +-- points to: demos/argocd/applications/ | +-- 1-simple-app.yaml (Application) +-- 2-helm-app.yaml (Application) +-- 3a-kustomize-dev.yaml (Application) +-- 3b-kustomize-prod.yaml (Application)Here is the parent Application from this demo:
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: app-of-apps namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io/foregroundspec: project: default source: repoURL: https://github.com/savitojs/k8s-learn-by-doing.git targetRevision: HEAD path: demos/argocd/applications destination: server: https://kubernetes.default.svc namespace: argocd syncPolicy: automated: prune: true selfHeal: true allowEmpty: false syncOptions: - Validate=true - CreateNamespace=true - PrunePropagationPolicy=foreground - PruneLast=true - Cascade=trueNotice the destination.namespace is argocd, not an application namespace. That is because the resources this Application manages are other Application CRDs, and those must live in the argocd namespace.
When to use it
Section titled “When to use it”- You have multiple applications and want a single
kubectl applyto deploy all of them. - You want adding a new application to be as simple as adding a YAML file to a directory and pushing to Git.
- You want a single delete to tear down everything cleanly (via finalizers).
- You are managing environments where each environment gets its own set of applications.
When not to use it
Section titled “When not to use it”- You have only one or two applications. Just apply them directly.
- You need fine-grained sync control per application (though you still get that, since each child Application has its own syncPolicy).
- Your applications span multiple repos with different access controls. Consider ApplicationSets instead.
How it works in this demo
Section titled “How it works in this demo”- You apply
app-of-apps.yaml. ArgoCD creates one Application. - ArgoCD syncs the parent. It reads
demos/argocd/applications/and finds four YAML files. - It creates four child Application resources in the
argocdnamespace. - Each child Application triggers its own sync loop, deploying resources to its respective namespace.
- Within seconds, you have four applications running across four namespaces, all managed from Git.
To add a fifth application, you create 5-new-app.yaml in the applications/ directory and push. The parent auto-syncs, creates the new child Application, and the child deploys its resources. No kubectl apply needed.
7. ArgoCD Projects for RBAC
Section titled “7. ArgoCD Projects for RBAC”ArgoCD Projects are a boundary mechanism. They control which repositories an Application can pull from, which clusters and namespaces it can deploy to, and which Kubernetes resource types it can create.
Here is the development project from this demo:
apiVersion: argoproj.io/v1alpha1kind: AppProjectmetadata: name: development namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.iospec: description: Development environment project sourceRepos: - "https://github.com/savitojs/k8s-learn-by-doing.git" - "https://helm.nginx.com/stable" - "https://charts.bitnami.com/bitnami" destinations: - namespace: "dev-*" server: https://kubernetes.default.svc - namespace: "default" server: https://kubernetes.default.svc - namespace: "kustomize-example" server: https://kubernetes.default.svc clusterResourceWhitelist: - group: "" kind: Namespace - group: rbac.authorization.k8s.io kind: ClusterRole - group: rbac.authorization.k8s.io kind: ClusterRoleBinding namespaceResourceWhitelist: - group: "" kind: Service - group: "" kind: ServiceAccount - group: "" kind: ConfigMap - group: "" kind: Secret - group: apps kind: Deployment - group: apps kind: ReplicaSet - group: "" kind: Pod - group: networking.k8s.io kind: Ingress - group: networking.k8s.io kind: NetworkPolicysourceRepos
Section titled “sourceRepos”Restricts which Git repos or Helm chart repos applications in this project can use. An Application referencing a repo not in this list will be rejected. In this demo, the project allows the main demo repo plus two Helm chart repos (nginx and bitnami).
The default project allows all repos (*). Custom projects should always be explicit.
destinations
Section titled “destinations”Restricts which cluster/namespace combinations are valid. Notice the wildcard dev-*, which allows any namespace prefixed with dev-. This is a common pattern: give each environment a namespace prefix, then use Projects to enforce boundaries.
An Application in this project targeting namespace: production on this cluster would be rejected. That is the point.
clusterResourceWhitelist
Section titled “clusterResourceWhitelist”Cluster-scoped resources (Namespaces, ClusterRoles, ClusterRoleBindings) are powerful. This whitelist explicitly names which cluster-scoped types this project is allowed to create. Without this list, the project cannot create any cluster-scoped resources.
This project allows creating Namespaces and RBAC resources, but not things like CustomResourceDefinitions or PersistentVolumes. That limits the blast radius.
namespaceResourceWhitelist
Section titled “namespaceResourceWhitelist”Same idea, but for namespace-scoped resources. This project can create Services, ConfigMaps, Secrets, Deployments, and a few other common types. It cannot create, say, a PodDisruptionBudget or a HorizontalPodAutoscaler, because those are not on the list.
This is defense in depth. Even if someone has access to create Applications in this project, they can only deploy a limited set of resource types.
Using a project
Section titled “Using a project”To assign an Application to a project, set spec.project:
spec: project: development # instead of "default"The applications in this demo currently use project: default for simplicity. In a real environment, you would assign them to scoped projects like development or production.
8. How ArgoCD Handles Helm vs Kustomize vs Plain Manifests
Section titled “8. How ArgoCD Handles Helm vs Kustomize vs Plain Manifests”ArgoCD’s repo server auto-detects the manifest format based on what it finds in the source path.
Plain manifests
Section titled “Plain manifests”If the path contains .yaml or .json files without a Chart.yaml or kustomization.yaml, ArgoCD treats every file as a raw Kubernetes manifest. No rendering, no templating. What you see in Git is what gets applied.
This is what happens with simple-app:
source: repoURL: https://github.com/savitojs/k8s-learn-by-doing.git targetRevision: HEAD path: demos/simple-app/manifestsArgoCD reads all YAML files in that directory and applies them as-is.
Helm charts
Section titled “Helm charts”If the path contains a Chart.yaml, ArgoCD treats it as a Helm chart. It runs the equivalent of helm template to render the manifests, then applies the result.
This is what happens with helm-app:
source: repoURL: https://github.com/savitojs/k8s-learn-by-doing.git targetRevision: HEAD path: demos/helm/chartYou can override Helm values without modifying the chart by adding a helm block to the source:
source: path: demos/helm/chart helm: valueFiles: - values-production.yaml parameters: - name: replicaCount value: "3" values: | ingress: enabled: trueKey detail: ArgoCD does not use helm install or helm upgrade. It uses helm template to generate manifests, then manages those manifests itself. This means Helm hooks, Helm rollback, and helm ls do not apply. ArgoCD owns the lifecycle, not Helm.
Kustomize overlays
Section titled “Kustomize overlays”If the path contains a kustomization.yaml, ArgoCD runs kustomize build and applies the result.
This is what happens with kustomize-dev and kustomize-prod:
# Dev overlaysource: path: demos/kustomize/overlays/development
# Prod overlaysource: path: demos/kustomize/overlays/productionSame base manifests, different overlays. Each overlay applies its own patches, labels, replica counts, or resource limits. ArgoCD deploys each to a separate namespace.
You can customize Kustomize behavior in the Application spec:
source: path: demos/kustomize/overlays/development kustomize: namePrefix: dev- images: - myapp=myregistry/myapp:v2.0Why this matters
Section titled “Why this matters”The auto-detection means you do not need to tell ArgoCD what kind of source you are using. Drop a Helm chart in a directory, point an Application at it, and ArgoCD figures out the rest. Switch from plain manifests to Kustomize by adding a kustomization.yaml, and ArgoCD adapts on the next sync.
This demo shows all three approaches deployed side by side, managed by the same ArgoCD instance, through the same App-of-Apps pattern.
9. Common GitOps Workflows
Section titled “9. Common GitOps Workflows”Adding a new application
Section titled “Adding a new application”- Create a new Application YAML in
applications/:
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata: name: my-new-app namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io/foregroundspec: project: default source: repoURL: https://github.com/savitojs/k8s-learn-by-doing.git targetRevision: HEAD path: demos/my-new-app/manifests destination: server: https://kubernetes.default.svc namespace: my-new-app syncPolicy: automated: prune: true selfHeal: true allowEmpty: false syncOptions: - Validate=true - CreateNamespace=true - PrunePropagationPolicy=foreground - PruneLast=true- Commit and push. The
app-of-appsparent auto-syncs, discovers the new file, creates the Application, and the child syncs its resources. Done.
Updating an application
Section titled “Updating an application”Change the manifests in the source path (e.g., bump an image tag, change replica count, add an environment variable). Commit and push. ArgoCD detects the change and syncs automatically.
# Example: update image tagsed -i 's|image: nginx:1.25|image: nginx:1.26|' demos/simple-app/manifests/deployment.yamlgit add . && git commit -m "bump nginx to 1.26" && git pushArgoCD picks up the change within 3 minutes (or instantly with webhooks).
Promoting between environments
Section titled “Promoting between environments”With Kustomize overlays, promotion is a Git operation. This demo has separate overlays for dev and prod, each deployed by its own Application:
kustomize-devwatchesdemos/kustomize/overlays/developmentkustomize-prodwatchesdemos/kustomize/overlays/production
To promote a change from dev to prod, update the production overlay to match what you tested in dev. Commit and push. ArgoCD syncs the production Application.
This can be a manual commit, a PR-based review process, or an automated promotion pipeline that copies values from one overlay to another after tests pass.
Rolling back
Section titled “Rolling back”GitOps rollback is a Git revert.
git revert HEADgit pushArgoCD detects the revert commit and syncs the previous state. The cluster goes back to what it was before the bad change.
You can also roll back from the ArgoCD UI or CLI:
# Roll back to a specific Git revisionargocd app sync simple-app --revision abc123But this is a temporary override. On the next auto-sync cycle, ArgoCD will re-sync to HEAD. For a persistent rollback, revert in Git.
Removing an application
Section titled “Removing an application”Delete the Application YAML from the applications/ directory and push. The parent Application’s prune: true setting detects the missing file and deletes the child Application. The child’s finalizer kicks in and deletes all managed resources. Clean removal, end to end.
10. Troubleshooting
Section titled “10. Troubleshooting”Application stuck in “OutOfSync”
Section titled “Application stuck in “OutOfSync””Symptoms: The application shows OutOfSync even after a manual sync.
Common causes:
-
Defaulted fields: Kubernetes adds default values to resources (e.g.,
strategy.rollingUpdateon Deployments). ArgoCD sees these as differences between the desired state (your manifest) and the live state (with defaults filled in). Fix: add the defaulted fields to your manifest, or configure resource customizations to ignore them. -
Mutating webhooks: An admission webhook modifies resources after ArgoCD applies them. ArgoCD sees the modification as drift. Fix: configure diff customizations to ignore the mutated fields.
-
Helm hooks: If you have Helm hooks that create resources, those resources may show as OutOfSync since ArgoCD manages the lifecycle differently than Helm. Fix: annotate hook resources with
argocd.argoproj.io/hook.
Check what ArgoCD sees as different:
argocd app diff simple-appApplication stuck in “Syncing”
Section titled “Application stuck in “Syncing””Symptoms: The sync operation starts but never completes.
Common causes:
- Resource health check hanging: ArgoCD waits for resources to become “Healthy.” If a Deployment cannot pull its image or a Pod is crash-looping, the sync will not complete. Check the pod status:
kubectl get pods -n simple-appkubectl describe pod <pod-name> -n simple-app- Resource dependencies: A resource depends on something that does not exist yet (e.g., a ConfigMap referenced by a Deployment). ArgoCD applies resources in waves, but sometimes the ordering is wrong. Fix: use sync waves with the
argocd.argoproj.io/sync-waveannotation.
Application stuck on deletion
Section titled “Application stuck on deletion”Symptoms: You delete an Application, but it hangs in a “Deleting” state.
Common causes:
- Finalizer waiting for resources to be deleted: The finalizer tells ArgoCD to delete all managed resources first. If a managed resource is stuck (e.g., a namespace with a finalizer of its own, or a PVC waiting for a pod to unmount), the Application deletion hangs.
Check what is stuck:
kubectl get all -n simple-appkubectl get pvc -n simple-app- Stuck namespace: If
CreateNamespace=truewas used and the namespace has resources that will not delete (e.g., resources from another controller), the entire chain stalls.
Nuclear option (use carefully): remove the finalizer from the Application to let it be deleted without cleaning up managed resources.
kubectl patch application simple-app -n argocd \ --type json -p='[{"op": "remove", "path": "/metadata/finalizers"}]'Then manually clean up the orphaned resources.
Sync failed with “ComparisonError”
Section titled “Sync failed with “ComparisonError””Symptoms: The application shows a ComparisonError instead of Synced/OutOfSync.
Common causes:
- Invalid manifests: A YAML file in the source path has syntax errors or references invalid API versions. Check the repo server logs:
kubectl logs -n argocd -l app.kubernetes.io/component=repo-server --tail=50- Repo access failure: ArgoCD cannot clone the repository. Check that the repo URL is correct and credentials are configured. In this demo, the repo is configured in
terraform/values.yaml:
configs: repositories: github.com: url: https://github.com/savitojs/k8s-learn-by-doing.git insecure: "true" insecureIgnoreHostKey: "true"For private repos, you would need SSH keys or a personal access token configured here.
Self-heal keeps reverting my changes
Section titled “Self-heal keeps reverting my changes”This is working as intended. Self-heal exists to prevent manual cluster drift. If you need to make a change, make it in Git. If you need to temporarily disable self-heal for debugging:
argocd app set simple-app --self-heal=falseRemember to re-enable it when you are done.
Resources not being pruned
Section titled “Resources not being pruned”Symptoms: You deleted a manifest from Git, but the resource still exists in the cluster.
Common causes:
- prune is not enabled: Check that
automated.prune: trueis set in the Application’s syncPolicy. - Resource has the
argocd.argoproj.io/compare-options: IgnoreExtraneousannotation: This tells ArgoCD to ignore the resource during comparison. It will not be pruned. - Resource is not tracked by this Application: ArgoCD tracks resources using labels. If a resource was created outside of ArgoCD or by a different Application, this Application will not prune it.
Quick Reference
Section titled “Quick Reference”| Concept | What it does | Where it is configured |
|---|---|---|
| Auto-sync | Syncs on Git changes without manual trigger | syncPolicy.automated |
| Self-heal | Reverts manual cluster changes to match Git | syncPolicy.automated.selfHeal: true |
| Auto-prune | Deletes resources removed from Git | syncPolicy.automated.prune: true |
| Finalizer | Ensures managed resources are deleted when the Application is deleted | metadata.finalizers |
| App-of-Apps | Application that manages other Applications | Source path contains Application YAMLs |
| Project | RBAC boundary for repos, namespaces, and resource types | AppProject CRD |
| CreateNamespace | Creates target namespace automatically | syncOptions: CreateNamespace=true |
| PruneLast | Deletes old resources only after new ones are healthy | syncOptions: PruneLast=true |