The Application resource
Source, destination, and sync.
A work order tacked to a job board names three things: the warehouse to pull parts from, the loading dock to deliver them to, and the manager who signed off. An Argo CD Application is that work order. Argo CD is the tool that keeps a Kubernetes cluster in step with configuration files stored in Git. Kubernetes is the system that runs your containers and picks which machine each one lands on. Git is the version-control system that records every change to a set of files. The Application is the record that ties one folder in a Git repository to one spot in your cluster, and almost everything Argo CD does hangs off it. Drift detection, health readouts, auto-healing: all of it reads this one object. So the three fields to learn first map straight onto that work order: where the files come from (source), where they land (destination), and who fenced in what the job may touch (project). Writing the order does not run it. That takes one more action, the first sync, and that is where this lesson ends up.
The Work Order Lives Where Argo CD Lives
An Application is a Kubernetes custom resource (a new object type that someone taught the cluster to understand, on top of the built-in ones like Pod and Service). Its kind is Application, and its apiVersion is argoproj.io/v1alpha1. That string is a group plus a version: argoproj.io is the API group (the family name Kubernetes uses to bundle related object types) and v1alpha1 is the version of that type's schema. Here is the part that catches people. The Application object lives in the namespace where Argo CD itself runs, conventionally one called argocd. A namespace is a named partition inside the cluster, the way a shared office fridge has a labeled shelf per team so nobody's lunch collides. The workloads the Application deploys usually land in a different namespace entirely. So the record and the things it manages sit in two separate places, and forgetting that leads to a specific class of mistakes we hit at the end.
apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata:name: guestbooknamespace: argocd # where Argo CD runs, NOT where the app deploysspec:project: default # the AppProject that fences in what this app may dosource:repoURL: https://github.com/argoproj/argocd-example-apps.gittargetRevision: HEAD # a branch, a tag, or a commit SHApath: guestbook # folder in the repo that holds the manifestsdestination:server: https://kubernetes.default.svc # the in-cluster API endpointnamespace: guestbook # target namespace for the workloads
Three fields carry the weight. spec.project names an AppProject, the manager who signed off: an Argo CD object that fences in what a group of Applications is allowed to touch (which repos, which clusters, which namespaces). spec.source points at the Git content. spec.destination says where the rendered result goes. And because the Application is itself a chunk of YAML (a plain-text format for structured configuration), you can commit it to Git alongside your app, so even the wiring is version-controlled. Hold that thought; it changes how you create and how you delete these objects.
Source: Pin Exactly What Ships
spec.source has three pieces. repoURL is the repository address. path is the folder inside it, relative to the repo root, and for this lesson it holds plain manifests (the YAML files that describe Kubernetes objects). Sources that render through Helm or Kustomize (two tools that build manifests from templates or layered overlays instead of storing them as-is) are their own lesson. targetRevision decides which version of that folder Argo reads. Set it to HEAD (Git's pointer to the newest commit on the default branch) and Argo tracks whatever lands on top of that branch. Set it to a tag or a commit SHA (the unique fingerprint Git stamps on every commit) and Argo reads exactly that frozen point, nothing else.
Destination: Which Cluster, Which Namespace
spec.destination names a cluster in one of two ways. server is the raw API endpoint URL; the special value https://kubernetes.default.svc means the same cluster Argo CD is running in, reached from inside it. name is a friendly label Argo assigns when you register an external cluster with argocd cluster add. Use server for the in-cluster shortcut, and switch to name once you run more than one cluster, because prod-eu-1 is easier to read and harder to fumble than a long API URL. Either way, namespace sets where the workloads land, which, again, is not the argocd namespace the Application lives in.
Two Ways to Create the Object
You can hand the fields to the command-line tool and let it build the object, or you can write the YAML yourself and apply it. The imperative form (you list the pieces on the command line and the tool assembles the object) is quick for a throwaway: argocd app create builds the same Application under the hood.
# imperative: Argo builds the Application object from these flagsargocd app create guestbook \--repo https://github.com/argoproj/argocd-example-apps.git \--path guestbook \--dest-server https://kubernetes.default.svc \--dest-namespace guestbook \--project default# to target a registered cluster by its friendly name instead of a URL:# argocd app create guestbook --dest-name prod-eu-1 ...
application 'guestbook' created
For anything that outlives a demo, write the manifest and apply it with kubectl. This declarative form is the GitOps way (running your cluster from files kept in Git, so the repository stays the single source of truth), because the Application itself now lives in a commit you can review, revert, and audit. The wiring becomes data, not a command you typed once and forgot.
# declarative: commit application.yaml, then apply it to the argocd namespacekubectl apply -n argocd -f application.yaml
application.argoproj.io/guestbook created
Creating Is Not Deploying
Creating the Application registers intent. It does not apply anything to the destination on its own. With no sync policy set (automatic sync is a later lesson), the app is born OutOfSync and waits for you to push the first apply. Read its state before you touch anything with argocd app get.
argocd app get guestbook
Name: argocd/guestbookProject: defaultServer: https://kubernetes.default.svcNamespace: guestbookURL: https://argocd.example.com/applications/guestbookSource:- Repo: https://github.com/argoproj/argocd-example-apps.gitTarget: HEADPath: guestbookSyncWindow: Sync AllowedSync Policy: <none>Sync Status: OutOfSync from HEAD (53e28ff)Health Status: MissingGROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGEService guestbook guestbook-ui OutOfSync Missingapps Deployment guestbook guestbook-ui OutOfSync Missing
OutOfSync means the cluster does not yet match the source. Missing health means the objects named in the manifests do not exist in the cluster at all, which is exactly right for an app that has never been synced. Now push the opening deploy. argocd app sync tells the controller (the background loop that keeps comparing your declared intent against the live cluster and nudges reality toward it) to render the source and apply it to the destination.
argocd app sync guestbook
TIMESTAMP GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE2026-07-20T10:14:02Z Service guestbook guestbook-ui OutOfSync Missing2026-07-20T10:14:02Z apps Deployment guestbook guestbook-ui OutOfSync Missing2026-07-20T10:14:05Z Service guestbook guestbook-ui Synced Healthy2026-07-20T10:14:05Z apps Deployment guestbook guestbook-ui Synced HealthyName: argocd/guestbookSync Status: Synced to HEAD (53e28ff)Health Status: HealthyOperation: SyncSync Revision: 53e28ff20cc530b9ada2173fbbd64d48338583baPhase: SucceededStart: 2026-07-20 10:14:02 +0000 UTCFinished: 2026-07-20 10:14:05 +0000 UTCDuration: 3sMessage: successfully synced (all tasks run)GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGEService guestbook guestbook-ui Synced Healthy service/guestbook-ui createdapps Deployment guestbook guestbook-ui Synced Healthy deployment.apps/guestbook-ui created
The sync ran to Succeeded, and the two objects flipped to Synced and Healthy. The Sync Revision in that output is the commit that actually shipped; keep it, because during an incident it is the fastest honest answer to what is running right now. Confirm from the Kubernetes side, and notice that the query for the Application runs against the argocd namespace while the Pods it created live in guestbook.
kubectl get applications -n argocdkubectl get pods -n guestbook
NAME SYNC STATUS HEALTH STATUSguestbook Synced HealthyNAME READY STATUS RESTARTS AGEguestbook-ui-6b689df5cd-4n2xq 1/1 Running 0 41s
Deletion Is Where People Get Burned
Deleting an Application asks a question Kubernetes answers with a finalizer. A finalizer works like the confirmation an uninstaller shows before it also wipes your saved files: it is a tag on an object that says run this cleanup before you actually remove me. Add resources-finalizer.argocd.argoproj.io to the Application's metadata.finalizers, and deleting the Application performs a cascading delete: Argo tears down every workload it deployed. Leave the finalizer off, and deleting the Application removes only the record; the workloads keep running with nothing reconciling them, orphaned.
metadata:name: guestbooknamespace: argocdfinalizers:- resources-finalizer.argocd.argoproj.io # delete the Application -> delete its workloads
# wrong namespace: no -n argocd, so kubectl looks in the default namespacekubectl delete application guestbook
Error from server (NotFound): applications.argoproj.io "guestbook" not found
Before you ever run a delete on a shared cluster, print the object first: kubectl get application guestbook -n argocd -o yaml and read the finalizers list. If resources-finalizer.argocd.argoproj.io is there, deleting the Application takes its workloads with it; if it is not, you will be cleaning up orphans by hand. Check the list, then decide.
Try this
Run kubectl apply -n argocd -f application.yaml on a scratch host or disposable cluster and read the output against what this lesson described. Then change one input so it fails, and re-run: the error you get is the one you will meet in production.
Takeaway
The trap worth remembering here: hEAD Is a Moving Target an Attacker Can Push To. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.