How to Install ArgoCD and Deploy Your First GitOps Application on Kubernetes
A beginner's guide to installing ArgoCD on a local Kubernetes cluster, deploying a real application through GitOps, and seeing how Argo CD detects and corrects configuration drift.
Argo CD is one of the three tools that a recent freeCodeCamp platform engineering guide named as the core of a modern Internal Developer Platform (IDP): Backstage as the developer portal, Crossplane for provisioning cloud infrastructure as Kubernetes resources, and Argo CD as the GitOps engine that actually deploys everything. That third piece, GitOps, is the one every team can start using today without first standing up the other two. This tutorial teaches it from scratch: what GitOps means, how Argo CD implements it, and how to install Argo CD on your own machine and deploy a real application with it.
Table Of Content
- Prerequisites
- Step 1: Create a local Kubernetes cluster with kind
- Install kind
- Install kubectl
- Create the cluster
- Step 2: Install Argo CD into the cluster
- Apply the Argo CD manifests
- Watch the components start
- Step 3: Install and log in with the Argo CD CLI
- Install the CLI
- Access the API server
- Retrieve the initial admin password and log in
- Step 4: Deploy your first application with GitOps
- Create the Application
- Inspect its status before syncing
- Sync (deploy) the Application
- Confirm the app is actually running
- Step 5: See GitOps drift detection in action
- Common mistakes and gotchas
- Forgetting the port-forward is still needed
- Confusing “OutOfSync” with “broken”
- Expecting Manual sync policy to self-heal
- Architecture mismatches on ARM64 hosts
- Cleaning up
- You’ve deployed your first GitOps application. What’s next?
GitOps is a way of running deployments where a Git repository, not a person typing commands, is the single source of truth for what should be running in a cluster. You describe the desired state (which application, which version, how many copies) as files committed to Git. A controller running inside the cluster continuously compares that desired state against what is actually running, and either reports the difference or corrects it automatically. Argo CD is the open source, CNCF-hosted tool that implements this pattern for Kubernetes. Instead of running kubectl apply by hand every time something changes, you commit the change to Git and Argo CD takes care of the rest.
By the end of this tutorial you will have a local Kubernetes cluster running Argo CD, a real application deployed to it straight from a public Git repository, and a hands-on look at what happens when someone changes that application outside of Git. Every command in this guide was executed on a fresh Ubuntu machine while writing it, using the exact tool versions named below, so the output shown is real output, not paraphrased from documentation.
Prerequisites
- A Linux machine with a bash shell and
sudoaccess. These steps were verified on Ubuntu 24.04 LTS; other modern Linux distributions work the same way once Docker is installed. - Docker Engine installed and running. kind (the tool this guide uses to create a local Kubernetes cluster) runs each cluster node as a Docker container, so Docker has to be in place first. Confirm it works with
docker ps: an empty table with no error means you are ready. - An x86_64 (AMD64) machine is recommended. The example application used later in this guide only publishes AMD64 container images; on ARM64 hosts (such as Apple Silicon under a Linux VM) you would need to substitute a multi-arch sample application.
- At least 2 free CPU cores and 4 GB of free RAM. Neither kind nor Argo CD is heavy, but both need headroom to start their components without fighting each other for CPU time.
- Basic comfort with the command line. No prior Kubernetes or GitOps experience is assumed; every concept is explained the first time it comes up.
- No cloud account and no signup with any third-party service. Everything in this guide runs entirely on your own machine.
Step 1: Create a local Kubernetes cluster with kind
kind (Kubernetes IN Docker) runs a full, real Kubernetes cluster inside Docker containers on your machine. It exists mainly for testing and local development, which makes it the fastest way to get a real cluster to practice on without touching a cloud provider.
Install kind
# Context: Ubuntu 24.04 LTS or similar, x86_64, with Docker already installed.
# Purpose: download and install kind v0.32.0, the current stable release.
[ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.32.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
kind version
Expected output: kind v0.32.0 go1.26.3 linux/amd64. If you are on an ARM64 machine, use the kind-linux-arm64 build instead; the official kind Quick Start guide lists both.
Install kubectl
kubectl is the standard Kubernetes command-line client. kind does not install it for you, and you need it for almost every step from here on.
# Context: same machine, any working directory.
# Purpose: download the current stable kubectl release and install it to your PATH.
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
rm -f kubectl
kubectl version --client
Expected output: a Client Version line (for example v1.36.2) and a Kustomize Version line. The exact version will track whatever is current stable at the time you run this; that is by design, since the command always resolves the latest stable release rather than a number hardcoded into this guide.
Create the cluster
# Context: Docker running, kind and kubectl both installed.
# Purpose: create a single-node local Kubernetes cluster named "kind".
kind create cluster
Expected output is a short progress log ending with:
Set kubectl context to "kind-kind"
You can now use your cluster with:
kubectl cluster-info --context kind-kind
kind automatically points your local kubeconfig at the new cluster, so every kubectl command from here on already targets it. Confirm the node is healthy:
# Context: right after cluster creation.
# Purpose: confirm the cluster's single node is registered and ready.
kubectl get nodes
Explanation: immediately after creation the node commonly shows STATUS: NotReady for a few seconds while the pod network (CNI) finishes initializing. That is normal, not an error. Re-run the command; on the machine used for this guide the node flipped to Ready within about 20 seconds. If it is still NotReady after two full minutes, something upstream (usually Docker resource limits) is the actual problem.
Step 2: Install Argo CD into the cluster
With a working cluster in place, the next step is installing Argo CD itself, which runs as a set of pods inside the cluster it manages.
Apply the Argo CD manifests
# Context: kubectl pointed at your new kind cluster (the default after Step 1).
# Purpose: create a dedicated namespace and install Argo CD v3.4.5 into it.
kubectl create namespace argocd
kubectl apply -n argocd --server-side --force-conflicts \
-f https://raw.githubusercontent.com/argoproj/argo-cd/v3.4.5/manifests/install.yaml
Expected output is a long list of lines ending in serverside-applied or created, one per Kubernetes object Argo CD needs (config maps, secrets, services, deployments, a stateful set, and several network policies). This guide pins the install to v3.4.5, the current stable release, instead of following Argo CD’s floating stable branch; the official Getting Started guide recommends pinning a specific version for exactly this reason: a floating branch can install a different version tomorrow than it does today.
Explanation: --server-side is not optional here. A few of Argo CD’s CustomResourceDefinitions (like the one for ApplicationSets) are larger than the 262 KB annotation limit that ordinary client-side kubectl apply enforces. Server-side apply avoids that limit entirely by not storing that annotation. --force-conflicts lets this apply take ownership of fields even if some other tool touched them first, which will not normally happen on a brand-new cluster but is harmless to include.
Watch the components start
# Context: right after applying the manifests.
# Purpose: wait for every Argo CD pod to report Ready, then list them.
kubectl wait --for=condition=Ready pods --all -n argocd --timeout=240s
kubectl get pods -n argocd
Expected output: seven pods, all 1/1 Running: argocd-application-controller-0, argocd-applicationset-controller, argocd-dex-server, argocd-notifications-controller, argocd-redis, argocd-repo-server, and argocd-server. On the machine used for this guide, all seven were Running within 33 seconds of applying the manifests. Each has a distinct job: argocd-server is the API and web UI, argocd-repo-server talks to Git repositories, argocd-application-controller is the reconciliation loop that compares Git to the live cluster, argocd-dex-server handles authentication, and argocd-redis caches state for the others.
Step 3: Install and log in with the Argo CD CLI
The argocd CLI talks to the Argo CD API server and is how you will create and sync applications in this guide, rather than clicking through the web UI.
Install the CLI
# Context: same machine, Argo CD v3.4.5 already running in the cluster.
# Purpose: install the matching v3.4.5 argocd CLI so client and server versions line up.
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/download/v3.4.5/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
rm -f argocd-linux-amd64
argocd version --client
Expected output: argocd: v3.4.5+<short-commit> followed by build metadata. Keeping the CLI version matched to the server version avoids the compatibility warnings Argo CD prints when they drift apart.
Access the API server
Argo CD’s argocd-server component is only reachable from inside the cluster by default. Port-forwarding exposes it on your own machine temporarily, which is the simplest option for a local cluster like this one (a real production install would use an Ingress or a LoadBalancer instead, as covered in the Getting Started guide).
# Context: Argo CD pods all Running.
# Purpose: forward local port 8080 to the argocd-server Service's HTTPS port, in the background.
kubectl port-forward svc/argocd-server -n argocd 8080:443 &
Expected output: Forwarding from 127.0.0.1:8080 -> 8080. That second number looks like a typo at first glance since the command specified 8080:443, but it is correct: kubectl port-forward reports the container’s actual listening port, and the argocd-server Service maps both its 80 and 443 ports to the same container port, 8080, where Argo CD handles HTTP, gRPC, and TLS together on one socket. The service’s external port number (443) is what you asked to forward to; the number in the log line is what it resolved to internally. Leave this command running in its own terminal (or keep it backgrounded) for the rest of the guide.
Retrieve the initial admin password and log in
# Context: port-forward from the previous step still running.
# Purpose: read Argo CD's auto-generated initial admin password.
argocd admin initial-password -n argocd
Expected output: a random 16-character string. Argo CD generates this once on first install and stores it in a Kubernetes secret named argocd-initial-admin-secret. Now log in with it:
# Context: password copied from the previous command.
# Purpose: authenticate the argocd CLI against your local Argo CD instance.
argocd login localhost:8080 --username admin --password '<paste-the-password-here>' --insecure
Expected output: 'admin:login' logged in successfully followed by Context 'localhost:8080' updated. The --insecure flag is required here because this default installation serves its UI and API behind a self-signed TLS certificate; production installs replace that certificate instead of bypassing the check. Once logged in, the official docs recommend changing the password with argocd account update-password and then deleting the argocd-initial-admin-secret secret, since it otherwise sits in the cluster holding the original password in plain text.
Step 4: Deploy your first application with GitOps
With Argo CD installed and the CLI logged in, you can now point it at a real Git repository and let it deploy from that repository instead of running kubectl apply yourself.
Create the Application
This guide uses the guestbook example maintained by the Argo project itself, a small single-container demo web app, so there is nothing to build or fork first.
# Context: argocd CLI logged in.
# Purpose: point kubectl's default namespace at argocd, then register the guestbook app.
kubectl config set-context --current --namespace=argocd
argocd app create guestbook \
--repo https://github.com/argoproj/argocd-example-apps.git \
--path guestbook \
--dest-server https://kubernetes.default.svc \
--dest-namespace default
Expected output: application 'guestbook' created. This command told Argo CD four things: which Git repository to watch, which folder inside it holds the manifests (guestbook, which contains just a Deployment and a Service), which cluster to deploy to (https://kubernetes.default.svc means “the same cluster Argo CD itself is running in”), and which namespace to put the resources in.
Inspect its status before syncing
# Context: right after creating the Application.
# Purpose: see how Argo CD currently compares Git to the live cluster.
argocd app get guestbook
Explanation: if you run this within a second or two of creating the app, Sync Status and Health Status may show up blank; the controller needs a moment to run its first comparison. Wait a few seconds and run it again. You should then see Sync Policy: Manual, Sync Status: OutOfSync, and Health Status: Missing, with a small table listing the Service and Deployment that Git says should exist. “OutOfSync” here does not mean anything is broken; it means Argo CD has compared Git to the cluster, correctly found that these resources do not exist yet, and is waiting for you to tell it to act, because this Application’s sync policy is Manual rather than automated.
Sync (deploy) the Application
# Context: Application shows OutOfSync.
# Purpose: tell Argo CD to make the live cluster match Git.
argocd app sync guestbook
Expected output: a per-resource log ending with deployment.apps/guestbook-ui created and service/guestbook-ui created, followed by a summary showing Phase: Succeeded and Message: successfully synced (all tasks run). This is the one command that actually performs a kubectl apply equivalent on your behalf; everything before it only compared state.
Confirm the app is actually running
# Context: sync reported Succeeded.
# Purpose: check that the underlying Kubernetes objects are healthy, then reach the app directly.
kubectl -n default get pods
argocd app get guestbook
Explanation: right after syncing, Health Status typically reads Progressing while the container image pulls and the pod starts; on the machine used for this guide it reached Running in 13 seconds and Health Status became Healthy shortly after. To see the actual application, forward its Service to your machine the same way you did for Argo CD itself, on a different local port:
# Context: guestbook-ui pod Running.
# Purpose: reach the deployed application directly, bypassing Argo CD's own UI.
kubectl port-forward svc/guestbook-ui -n default 8081:80 &
curl -s http://localhost:8081/ | grep -o '<title>[^<]*</title>'
Expected output: <title>Guestbook</title>, confirmed with an HTTP 200 response. That is the real, deployed application answering a real HTTP request, not a health check. Note that this particular example only ships the front-end (guestbook-ui); the classic multi-tier guestbook demo pairs it with a Redis backend, which this manifest does not include, so the page renders but has nothing behind it to actually save a message to. That is expected for this tutorial: the goal is confirming Argo CD deployed and is serving the app, not exercising every feature of the demo itself.
Step 5: See GitOps drift detection in action
The entire point of GitOps is that Git, not whoever last ran a command, decides what is supposed to be running. You can see that directly by changing something outside of Git and watching Argo CD notice.
# Context: guestbook app Synced and Healthy.
# Purpose: simulate someone bypassing GitOps with a direct kubectl change.
kubectl -n default scale deployment guestbook-ui --replicas=2
argocd app get guestbook
Expected output: Sync Status: OutOfSync reappears, even though nothing was changed in Git and Health Status still reads Healthy (two running replicas is a perfectly healthy Kubernetes state, just not the state Git asked for). Ask Argo CD exactly what changed:
# Context: Application showing OutOfSync after the manual scale.
# Purpose: see a line-level diff between Git's desired state and the live cluster.
argocd app diff guestbook
Expected output is a unified diff showing replicas: 2 on the live side and replicas: 1 on the Git side. This is the core GitOps guarantee working exactly as designed: a manual change is visible and flagged instead of silently becoming the new normal. Because this Application’s sync policy is Manual (the default used throughout this guide), Argo CD reports the drift but will not touch it until you act. Fix it the same way as any other out-of-sync state:
# Context: drift confirmed via argocd app diff.
# Purpose: bring the live cluster back in line with Git.
argocd app sync guestbook
Explanation: this reverts the replica count back to 1 and returns the app to Synced and Healthy, because Git, not the manual kubectl scale command, is still the source of truth. In production you would usually go one step further and enable automated sync with self-healing (argocd app set guestbook --sync-policy automated, or spec.syncPolicy.automated.selfHeal: true in the Application’s own manifest), so that a drift like the one you just made gets corrected automatically within seconds instead of waiting for someone to notice and run sync by hand.
Common mistakes and gotchas
Forgetting the port-forward is still needed
Every argocd CLI command in this guide talks to localhost:8080, which only exists while the kubectl port-forward svc/argocd-server ... command from Step 3 is still running. If you close that terminal, restart your machine, or the process otherwise dies, every subsequent argocd command will fail to connect. Re-run the port-forward command to restore it; you do not need to log in again unless the cluster itself was also recreated.
Confusing “OutOfSync” with “broken”
A freshly created Application always starts OutOfSync, because nothing has been deployed yet; that is correct, expected behavior, not an error to troubleshoot. Only worry about OutOfSync after you expected a sync to have already happened.
Expecting Manual sync policy to self-heal
Step 5 deliberately used the default Manual sync policy so the drift would stick around long enough to inspect with argocd app diff. If you enable automated sync with selfHeal as suggested at the end of Step 5, that same manual kubectl scale command would be reverted within seconds, before you could look at it. Neither behavior is a bug; they are two different, deliberate policy choices.
Architecture mismatches on ARM64 hosts
The guestbook example app’s container image is only published for AMD64. If you are following this guide inside an ARM64 environment, the pod will sit in ImagePullBackOff or crash on startup instead of reaching Running. This is a limitation of the specific sample application, not of kind, Argo CD, or GitOps as a pattern.
Cleaning up
# Context: done experimenting.
# Purpose: remove the entire kind cluster, including Argo CD and the guestbook app, in one step.
kind delete cluster
Expected output: Deleted nodes: ["kind-control-plane"]. Because kind runs the whole cluster as Docker containers, deleting the cluster removes everything in it, Argo CD included, in a single command; there is no separate cleanup needed for the resources you created inside it.
You’ve deployed your first GitOps application. What’s next?
You now have a working mental model of the core GitOps loop: describe desired state in Git, let a controller compare it to reality, and either watch it report drift or let it correct that drift automatically. From here, the official Argo CD documentation covers the pieces this guide intentionally left out to stay focused: replacing port-forwarding with a real Ingress, registering additional remote clusters instead of deploying only to the one Argo CD runs in, and using ApplicationSets to manage many environments from one definition instead of creating each Application by hand.
Zooming out further, Argo CD is only one layer of the Internal Developer Platform pattern referenced at the start of this guide. The freeCodeCamp guide that inspired this tutorial pairs it with Backstage as a developer-facing software catalog and portal, and Crossplane to provision the underlying cloud infrastructure (databases, storage, message queues) as Kubernetes custom resources, so that a developer can request a database the same way they request a deployment: as a file committed to Git. Once GitOps itself feels comfortable, those are the natural next tools to explore.








No Comment! Be the first one.