GitOps Deployment Pipeline with ArgoCD
Build a local GitOps loop where a git push auto-deploys to Kubernetes.
Introduction
30 Second Summary
Every team that ships software has to get code from a developer's laptop onto a server. In most teams, someone runs a deploy command, crosses their fingers, and hopes nothing breaks.
In this project, you will build a fully automated local deployment pipeline using k3d and ArgoCD. You push a change to GitHub, and your Kubernetes cluster updates itself to match.
What You'll Build
You open the ArgoCD dashboard, push a manifest change to GitHub, and watch your app redeploy itself with zero manual commands.
By the end of this project, you'll have:
- A local Kubernetes cluster running inside Docker on your laptop, ready to host any containerized app you throw at it.
- An ArgoCD dashboard where you watch every deployment happen in real time, with full sync status and health indicators.
- A complete GitOps loop where pushing to GitHub automatically deploys your changes to the cluster with no manual intervention.
- Secret Mission: Break your own cluster on purpose and watch ArgoCD self-heal it back to the desired state.
Are there any prerequisites?
You need Docker Desktop installed and running on your machine. You also need a GitHub account and basic familiarity with running commands in a terminal.
No prior Kubernetes experience is required. This project teaches you everything from scratch.
Before We Start
Before We Start...
Before we dive in, let's lock in what you're about to build and why it matters. This project tackles a real deployment problem that platform engineering teams solve every day.
Install Tools and Create Your Cluster
Three tools stand between you and a working GitOps pipeline.
kubectl lets you talk to Kubernetes. k3d runs a lightweight cluster inside Docker. The ArgoCD CLI connects you to the GitOps controller you'll install later. In this step, you'll install all three via Homebrew and spin up a local Kubernetes cluster that runs entirely on your laptop.
In this step, get ready to:
- Install kubectl, k3d, and the ArgoCD CLI.
- Create a local Kubernetes cluster running inside Docker.
- Verify the cluster is healthy and ready for deployments.
Install kubectl, k3d, and the ArgoCD CLI
Each tool has a specific job. kubectl sends commands to your cluster. k3d creates and manages lightweight Kubernetes clusters inside Docker containers. The argocd CLI lets you interact with the ArgoCD server you'll deploy in a later step.
- Install all three tools by running these commands in your terminal:
brew install kubectl
brew install k3d
brew install argocd
What do these commands do?
Each brew install command downloads and installs a CLI tool from Homebrew's package registry. Homebrew handles dependencies and puts the binary in your PATH so you can run it from any terminal window.
- Verify each tool installed correctly by running these commands:
kubectl version --client
k3d version
argocd version --client
Each command prints a version number. If all three respond without errors, your tools are ready.
Seeing 'command not found'?
- Make sure Homebrew itself is installed. Run brew --version to check. If Homebrew is missing, install it from https://brew.sh first.
- If one tool failed but the others succeeded, try running the failing brew install command again. Temporary network issues can interrupt a download.
- Close and reopen your terminal after installing. Some shells need a fresh session to pick up new binaries.
Still stuck?
Create your k3d cluster with a port mapping
k3d creates Kubernetes clusters that run as Docker containers. The port mapping flag tells Docker to forward traffic from your laptop's port 30080 into the cluster, so you can reach your demo app later from the browser.
- Create a single-node cluster called gitops-demo by running this command:
k3d cluster create gitops-demo -p "30080:30080@server:0"
What does this command do?
- k3d cluster create gitops-demo spins up a new single-node Kubernetes cluster named gitops-demo inside Docker.
- -p "30080:30080@server:0" maps port 30080 on your laptop to port 30080 on the cluster's server node. This is how you'll access your demo app from localhost later.
The command takes about 15-30 seconds to complete. You'll see Docker pulling the k3s image and starting the cluster containers.
- Verify your cluster's node is running by running:
kubectl get nodes
You should see one node named k3d-gitops-demo-server-0 with a status of Ready.
Node not showing as Ready?
- Make sure Docker Desktop is running. k3d needs Docker to create cluster containers.
- If the status says NotReady, wait 30 seconds and run kubectl get nodes again. The node needs a moment to finish initializing.
- If you see error: no configuration has been provided, the kubectl context did not switch automatically. Run kubectl config use-context k3d-gitops-demo to point kubectl at your new cluster.
Need more help?
Verify the cluster is healthy
A running node is good. Now confirm the cluster's internal services are online and the expected namespaces exist.
Before you run the next command, what do you think the cluster endpoint address will look like? A remote URL, or something on localhost?
- Check the cluster endpoint by running:
kubectl cluster-info
You'll see the Kubernetes control plane running at a local address like https://0.0.0.0: followed by a port number. This confirms kubectl is talking to your local k3d cluster, not a remote server.
- Confirm the default namespaces are present by running:
kubectl get namespaces
You should see three namespaces: default, kube-system, and kube-public. These are standard Kubernetes namespaces that every healthy cluster creates on startup.
What are namespaces?
Namespaces are virtual subdivisions inside a Kubernetes cluster. They let you organize resources into groups. kube-system holds internal Kubernetes components. default is where your app resources will live unless you specify otherwise.
Your local Kubernetes cluster is running and healthy. Next up, you'll deploy an app the old-fashioned way to feel the pain that GitOps eliminates.
Deploy Your App Manually
Your cluster is running and ready to host workloads. Before you automate anything with ArgoCD, you will deploy an app the old-fashioned way: writing manifests and running kubectl apply by hand.
This manual approach works, but it has a critical gap. There is no audit trail, no rollback history, and no way to know if someone changed something behind your back. By the end of this step, you will feel that gap firsthand.
In this step, get ready to:
- Write Kubernetes manifests for an nginx Deployment and Service.
- Deploy the manifests manually with kubectl and verify the app is running.
- Make an untracked manual change to expose the audit-trail problem.
Write the Kubernetes manifests
A Kubernetes manifest is a YAML file that declares what you want running in your cluster. You need two: a Deployment (manages your pods) and a Service (exposes them to the network).
- In your terminal, create the k8s directory to hold your manifests by running this command:
mkdir k8s
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder and select your project folder (the one your terminal is open in).
- In the VS Code Explorer sidebar, right-click the k8s folder and select New File.
- Name the file deployment.yaml and press Enter.
- Paste the following YAML into k8s/deployment.yaml:
# Deployment: manages a set of identical pods
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
labels:
app: demo-app
spec:
# Run 2 copies of this pod
replicas: 2
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
# Use the official nginx web server image
- name: web
image: nginx:1.25
ports:
- containerPort: 80
What does this manifest do?
- replicas: 2 tells Kubernetes to run 2 identical copies (pods) of your container.
- image: nginx:1.25 pulls the official nginx web server from Docker Hub. No custom image build needed.
- The selector and labels connect the Deployment to the pods it manages. They must match.
- Save k8s/deployment.yaml with Cmd+S (macOS) or Ctrl+S (Windows).
- Confirm the file exists by running this in your terminal:
ls k8s/
You should see deployment.yaml listed in the output.
Don't see the file?
- Make sure you saved the file in VS Code. An unsaved file shows a dot on its tab.
- Check that your terminal is in the same project folder that VS Code opened. Run pwd to confirm.
Help me figure out why my file isn't showing up in the k8s directory.
- Back in VS Code, right-click the k8s folder again and select New File.
- Name the file service.yaml and press Enter.
- Paste the following YAML into k8s/service.yaml:
# Service: exposes your pods to network traffic
apiVersion: v1
kind: Service
metadata:
name: demo-app
spec:
# NodePort makes the service accessible on a fixed port on your machine
type: NodePort
selector:
app: demo-app
ports:
- port: 80
targetPort: 80
# The port you'll use to reach the app: localhost:30080
nodePort: 30080
What does this Service do?
- type: NodePort exposes your app on a fixed port on every node in your cluster.
- nodePort: 30080 is the port you will use on localhost to reach the app. You mapped this port when you created the k3d cluster in Step 1.
- The selector matches the app: demo-app label on your pods, routing traffic to the right containers.
- Save k8s/service.yaml.
- Confirm both files exist by running this in your terminal:
ls k8s/
You should see both deployment.yaml and service.yaml in the output.
Only see one file?
- Check the VS Code Explorer sidebar to make sure the second file is saved inside the k8s folder, not at the project root.
Help me find my missing YAML file.
✔️ Awesome, I've got everything!
Great. Make sure both files are saved in VS Code before continuing.
ⓧ I'd like to double check the full code
Here is the complete k8s/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
labels:
app: demo-app
spec:
replicas: 2
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: web
image: nginx:1.25
ports:
- containerPort: 80
And here is the complete k8s/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: demo-app
spec:
type: NodePort
selector:
app: demo-app
ports:
- port: 80
targetPort: 80
nodePort: 30080
Apply the manifests manually with kubectl
- Deploy your manifests to the cluster by running this command:
kubectl apply -f k8s/
You should see two lines confirming both resources were created: the Deployment and the Service.
- Check that your pods are running by running this command:
kubectl get pods
You should see 2 pods with names starting with demo-app- and a status of Running. It may take a few seconds for them to start.
Pods stuck in Pending or ContainerCreating?
- Wait 10-15 seconds and run kubectl get pods again. The first time, Kubernetes pulls the nginx image from Docker Hub.
- Make sure Docker Desktop is running. k3d runs inside Docker, so if Docker is stopped, nothing works.
My pods are not reaching Running status. Help me debug.
Before you run the next command, do you think you can already reach the app from your browser at localhost:30080? Why or why not?
- Verify the app is reachable by running this command:
curl http://localhost:30080
You should see the nginx welcome page HTML in your terminal output. The key line is Welcome to nginx! in the response body.
What just happened?
Your request travelled from localhost:30080 on your machine, through the k3d port mapping, into the cluster's NodePort Service, and finally to one of your 2 nginx pods. The deployment works.
Notice the problem
Your app is running. Now imagine a teammate wants more capacity. They hop on the cluster and scale up the deployment directly.
What do you think will happen if you change the replica count right now? Will there be any record of who did it or why?
- Manually scale the deployment to 5 replicas by running this command:
kubectl scale deployment demo-app --replicas=5
- Check the pods again by running:
kubectl get pods
You now see 5 pods running instead of 2. The scale command worked instantly.
Why is this a problem?
Ask yourself: who made this change? When? Why? There is no commit message, no pull request, no approval. Your deployment.yaml still says replicas: 2 but the cluster has 5.
This gap between what your files say and what actually runs is called drift. In a production environment, drift leads to outages nobody can explain. GitOps eliminates drift by making Git the single source of truth.
You just deployed an app and immediately hit the problem that plagues every team using manual kubectl commands. Next up, you will install ArgoCD to eliminate this gap for good.
Install ArgoCD
In the last step, you scaled the deployment to 5 replicas with a single command. Nobody reviewed it. Nobody approved it. There is no record of who did it or why.
That invisible drift is the core problem ArgoCD solves. ArgoCD is a Kubernetes controller that continuously watches a Git repo and ensures the cluster matches what is declared there. In this step, you will install ArgoCD into your cluster so it can take over deployment duties.
In this step, get ready to:
- Deploy ArgoCD into the cluster as a set of pods in a dedicated namespace.
- Access the ArgoCD web dashboard and log in as an admin.
Deploy ArgoCD into your cluster
ArgoCD runs inside your Kubernetes cluster as a collection of pods. It needs its own namespace to keep its resources separate from your application workloads.
- Create a dedicated namespace for ArgoCD by running this command:
kubectl create namespace argocd
You should see namespace/argocd created printed in your terminal.
- Install ArgoCD using the official stable manifest by running:
kubectl apply -n argocd --server-side --force-conflicts -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
This command downloads and creates all ArgoCD resources (Deployments, Services, ConfigMaps, RBAC rules) inside the argocd namespace. You will see a long list of resources printed as they are created.
What does this command do?
The manifest URL points to ArgoCD's stable release branch. It installs everything ArgoCD needs to run: the API server, the repo server (which clones your Git repos), the application controller (which reconciles cluster state), and supporting services.
The --server-side and --force-conflicts flags tell Kubernetes to handle large manifests efficiently and resolve any ownership conflicts during installation.
- Wait for all ArgoCD pods to become ready by running:
kubectl wait --for=condition=Ready pods --all -n argocd --timeout=120s
This may take up to a minute while container images are pulled. When complete, it prints a confirmation line for each pod.
- Confirm the pods are running by running:
kubectl get pods -n argocd
You should see several pods (including argocd-server, argocd-repo-server, and argocd-application-controller) all showing a Running status with all containers ready.
Pods stuck in Pending or CrashLoopBackOff?
Make sure Docker Desktop is running and has enough memory allocated (at least 4GB recommended). ArgoCD's pods need to pull several container images the first time.
If pods show ImagePullBackOff, check your internet connection. The images are pulled from public registries.
Still stuck? Help me debug ArgoCD pods that won't start in my k3d cluster.
Access the ArgoCD UI and retrieve the admin password
ArgoCD includes a web dashboard where you can see applications, sync status, and health at a glance. To reach it from your browser, you need to forward a local port to the ArgoCD server running inside the cluster.
- Open a new terminal window or tab. The port-forward command will occupy this terminal for as long as it runs.
- In the new terminal, start the port-forward by running:
kubectl port-forward svc/argocd-server -n argocd 8080:443
What does this command do?
This forwards traffic from localhost:8080 on your Mac to port 443 on the argocd-server Service inside the cluster. The command keeps running and does not return you to a prompt. Leave it running in this terminal.
- Switch back to your original terminal.
- Retrieve the initial admin password by running:
argocd admin initial-password -n argocd
This prints a password string. Copy it. You will need it to log in.
Password is temporary
This auto-generated password is stored in a Kubernetes secret. In a production setup you would change it immediately, but for this local demo it works as-is.
Before you open your browser, think about this: you are accessing a service over HTTPS that uses a self-signed certificate. What do you think your browser will say?
- Open https://localhost:8080 in your browser.
Seeing a certificate warning? That is expected.
ArgoCD uses a self-signed certificate by default. Your browser will warn you that the connection is not private. This is safe for local development.
In Chrome, click Advanced then Proceed to localhost (unsafe). In Safari, click Show Details then visit this website. In Firefox, click Advanced then Accept the Risk and Continue.
- In the login form, enter admin as the username.
- Paste the password you copied from the previous command.
- Click SIGN IN.
The ArgoCD dashboard loads. It shows an empty applications list. No applications are configured yet because you have not told ArgoCD which Git repo to watch.
Cannot reach localhost:8080?
Make sure the port-forward command is still running in your other terminal. If you see a connection refused error, the port-forward may have stopped.
Check that no other process is using port 8080 on your machine. If something else is on that port, stop the conflicting process and re-run the port-forward command.
Still cannot connect? Help me troubleshoot why I cannot access ArgoCD at localhost:8080.
ArgoCD is installed and ready. Right now it is watching nothing. In the next step, you will push your manifests to GitHub and tell ArgoCD to deploy whatever it finds there. From that point forward, Git becomes the single source of truth.
Connect ArgoCD to Your GitHub Repo
ArgoCD is installed and its dashboard is ready. Right now, though, it has nothing to deploy. The dashboard is empty because ArgoCD does not know where to look for your manifests.
In this step, you will push your manifests to GitHub and tell ArgoCD to watch that repo. From this point forward, Git is the single source of truth and kubectl apply is retired.
In this step, get ready to:
- Push your Kubernetes manifests to a public GitHub repo.
- Create an ArgoCD Application that watches the repo and auto-deploys changes.
- Watch ArgoCD deploy your app entirely from Git with no manual kubectl commands.
Push your manifests to a GitHub repo
ArgoCD needs a Git repository to watch. You will create a public repo on GitHub and push the k8s/ manifests you wrote in the previous step.
- Open your browser and go to https://github.com/new.
- Type gitops-demo in the **Repository name** field.
- Select Public for the repository visibility.
- Leave all other options unchecked (no README, no .gitignore).
- Click Create repository.
Why no README?
You already have local files to push. Initializing with a README creates a commit on GitHub that conflicts with your local commit history. Keeping the repo empty avoids merge conflicts.
GitHub shows a Quick Setup page with commands for pushing an existing repository. You will use those commands next.
- Switch back to your terminal. Make sure you are inside the project folder that contains the k8s/ directory.
- Initialize a git repo, commit your manifests, and push to GitHub by running these commands:
git init
git add .
git commit -m "Initial commit: add Kubernetes manifests"
git branch -M main
git remote add origin https://github.com/[[GITHUB_USERNAME="your-github-username"]]/gitops-demo.git
git push -u origin main
What do these commands do?
- git init turns the folder into a Git repository.
- git add . stages all files (your k8s/ directory and its contents).
- git branch -M main renames the default branch to main.
- git remote add origin connects your local repo to the GitHub repo you just created.
- git push -u origin main uploads your commits to GitHub.
- Go back to your GitHub repo page in the browser and refresh it.
- You should see the k8s/ folder listed with deployment.yaml and service.yaml inside it.
Push failed or authentication error?
- If you see a permission denied or authentication error, GitHub requires a personal access token or SSH key for pushes. Follow GitHub's instructions to set up authentication.
- If you see "remote origin already exists", run git remote set-url origin followed by your repo URL to update it.
- help me fix my git push authentication error
Delete the manual deployment and create an ArgoCD Application
Your cluster currently has 5 pods running from the manual scaling you did earlier. Those resources have no connection to Git. You need to remove them so ArgoCD can recreate them from the repo as the single source of truth.
- Delete the manually-deployed resources by running this command:
kubectl delete -f k8s/
- Confirm the pods are gone by running:
kubectl get pods
You should see "No resources found in default namespace." The cluster is now a blank slate for ArgoCD to manage.
Now you will create an ArgoCD Application resource. This is a Kubernetes custom resource that tells ArgoCD which repo to watch, which path contains manifests, and where to deploy them.
- Create a file called argocd-app.yaml in your project folder (the same folder that contains k8s/) by running:
touch argocd-app.yaml
- Open argocd-app.yaml in VS Code and paste the following content:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: demo-app
namespace: argocd
spec:
project: default
source:
# Your GitHub repo URL and path to manifests
repoURL: https://github.com/[[GITHUB_USERNAME="your-github-username"]]/gitops-demo.git
targetRevision: main
path: k8s
destination:
# Deploy to the local cluster's default namespace
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
# Revert any manual changes made to the cluster
selfHeal: true
# Delete resources removed from Git
prune: true
What does this manifest do?
- source.repoURL points ArgoCD at your GitHub repo.
- source.path: k8s tells ArgoCD to look inside the k8s directory for Kubernetes manifests.
- destination.server targets the local cluster (the one you are already connected to).
- syncPolicy.automated.selfHeal: true means ArgoCD reverts any manual changes someone makes directly to the cluster.
- syncPolicy.automated.prune: true means ArgoCD deletes cluster resources that have been removed from Git.
This is GitOps in one file. Git declares the desired state, and ArgoCD continuously enforces it.
- Save argocd-app.yaml.
- Apply it to your cluster by running this command:
kubectl apply -f argocd-app.yaml
You should see: application.argoproj.io/demo-app created.
Seeing a "no matches for kind Application" error?
- This means the ArgoCD CRDs are not installed. Check that all ArgoCD pods are running with kubectl get pods -n argocd. If pods are missing or in CrashLoopBackOff, reinstall ArgoCD using the install command from the previous step.
- help me fix ArgoCD CRD not found error
✔️ Awesome, I've got everything!
Great. Make sure you have saved argocd-app.yaml and confirm you saw the "created" message after applying it.
ⓧ I'd like to double check the full code
Here is the complete argocd-app.yaml for reference:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: demo-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/[[GITHUB_USERNAME="your-github-username"]]/gitops-demo.git
targetRevision: main
path: k8s
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
selfHeal: true
prune: true
Watch ArgoCD deploy from Git
You just told ArgoCD where to find your manifests. It will now read the repo, compare the desired state (Git) against the actual state (empty cluster), and create the missing resources automatically.
- Switch back to your browser and open the ArgoCD UI at https://localhost:8080.
- Refresh the page. Your demo-app application should now appear in the list.
Before you check the pods, predict what you expect to see. ArgoCD read your deployment.yaml which declares 2 replicas. Do you think you will see 2 pods, 5 pods, or 0 pods?
- Verify that ArgoCD recreated your deployment by running:
kubectl get pods
You should see 2 nginx pods with a recent AGE value (a few seconds old). ArgoCD read the manifest from Git and deployed exactly what was declared there.
- In the ArgoCD UI, click on demo-app to see the full resource tree.
- Confirm you see green Synced and Healthy badges at the top of the application view.
What just happened?
ArgoCD polled your GitHub repo, found the manifests in k8s/, and applied them to the cluster automatically. No one ran kubectl apply. Git is now the owner of your deployment.
Every change from this point forward goes through a git commit, not a manual command. That is the core promise of GitOps.
App stuck on OutOfSync or Progressing?
- Make sure your GitHub repo is public. ArgoCD cannot pull from a private repo without credentials configured.
- Check the repo URL in your argocd-app.yaml matches your actual GitHub repo URL exactly, including the .git suffix.
- Click on the app in the ArgoCD UI and check the Events tab for error messages about the sync.
- help me troubleshoot ArgoCD not syncing my application
Your cluster is now fully managed by Git. You retired kubectl apply and replaced it with a system that watches your repo and enforces the desired state automatically. Next up, you will close the loop by pushing a change to Git and watching ArgoCD update your running app with zero manual intervention.
Complete the GitOps Loop
Your ArgoCD application is watching your GitHub repo and ready to deploy whatever it finds there. Now for the real payoff.
You will change a single value in your deployment manifest, push it to GitHub, and watch your cluster update itself automatically. No kubectl apply. No SSH. No cluster credentials. This is how platform teams deploy at scale.
In this step, get ready to:
- Update the nginx image tag in your deployment manifest.
- Push the change to GitHub and trigger ArgoCD to sync.
- Verify the deployment shows the new image with zero manual intervention.
Update the image tag in your manifest
The GitOps loop starts with a change in Git. You will update the nginx image from version 1.25 to 1.27 in your deployment manifest.
- In VS Code, open k8s/deployment.yaml
- Find this line near the bottom of the file:
image: nginx:1.25
- Replace nginx:1.25 with nginx:1.27, so the line now looks like this:
image: nginx:1.27
- Save k8s/deployment.yaml.
✔️ Awesome, I've got everything!
Great. Double check you saved the file before moving on.
ⓧ I'd like to double check the full code
Here is the complete k8s/deployment.yaml after your edit:
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
labels:
app: demo-app
spec:
replicas: 2
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 80
Commit and push the change to GitHub
In a GitOps workflow, deploying means pushing to Git. Your commit message becomes the audit trail for this deployment.
- Commit and push your change by running these commands in your terminal:
git add k8s/deployment.yaml
git commit -m "Update nginx image to 1.27"
git push
What do these commands do?
- git add stages the edited file for commit.
- git commit -m records the change with a message that serves as your deployment log.
- git push sends the commit to GitHub, where ArgoCD is watching.
You should see output confirming the push to the main branch of your gitops-demo repo.
Push failed or authentication error?
- Make sure you are inside the gitops-demo directory when running git commands.
- If you see an authentication error, confirm your GitHub credentials are configured (you may need a personal access token for HTTPS push).
- help me fix a git push authentication error
Watch ArgoCD auto-sync
ArgoCD polls your GitHub repo every 3 minutes by default. To see the update happen instantly, you can trigger a manual sync from the ArgoCD UI.
- Switch back to the ArgoCD UI at https://localhost:8080 in your browser.
- Click on your demo-app application tile.
- Click the Sync button at the top of the application view.
- In the sync panel that appears, click Synchronize.
Prefer the CLI?
You can also trigger sync from the terminal. First login to the ArgoCD CLI:
argocd login localhost:8080 --insecure
Enter admin as the username and the password you retrieved earlier. Then run:
argocd app sync demo-app
Watch the ArgoCD UI. You will see the application briefly show an OutOfSync status, then transition back to Synced and Healthy as the new pods come up.
Before you run the next command, what do you think the image tag will show? Will it still be nginx:1.25 or will it have changed?
- Verify the deployment now uses the new image by running:
kubectl describe deployment demo-app | grep Image
You should see:
Image: nginx:1.27
The image updated from nginx:1.25 to nginx:1.27 without you ever running kubectl apply. ArgoCD read the change from Git and reconciled the cluster.
- Confirm the new pods are running by running:
kubectl get pods
You should see 2 pods with a recent AGE value (seconds or minutes old). These are fresh pods running nginx:1.27.
Still seeing nginx:1.25?
- Check that your git push succeeded. Run git log --oneline -1 to confirm your latest commit mentions the image update.
- Verify the push reached GitHub by visiting your repo in a browser and checking k8s/deployment.yaml shows nginx:1.27.
- If ArgoCD still shows the old image, click Sync again in the UI to force a re-sync.
- help me troubleshoot why ArgoCD is not syncing my deployment change
Understand the full loop
You just deployed to Kubernetes with a git push. Git is the single source of truth for your cluster's desired state.
Why does this matter?
Every change is tracked in git history: who changed what, when, and why (the commit message). There is no mystery about what is running in production or who put it there.
If something breaks, you can git revert the commit and ArgoCD rolls the cluster back to the previous state. No one needs cluster credentials to deploy or rollback.
Secret mission
Break Things and Watch Self-Healing
Your GitOps pipeline deploys automatically. But what happens when someone bypasses Git and edits the cluster directly? Break your own deployment on purpose and discover whether ArgoCD can heal itself back to the desired state.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally inside Docker, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- A k3d cluster named gitops-demo running as Docker containers.
- ArgoCD deployed inside the cluster (in the argocd namespace).
- A GitHub repo called gitops-demo with your Kubernetes manifests.
- Local project files (k8s/ directory and argocd-app.yaml).
Keep everything running
No action needed. Choose this if you plan to keep experimenting with your GitOps pipeline or want to demo it soon.
- Your k3d cluster stays running as long as Docker Desktop is running. It consumes approximately 1-2GB of RAM.
- The kubectl, k3d, and argocd CLI tools remain installed on your machine via Homebrew.
- ArgoCD continues to poll your GitHub repo every 3 minutes and auto-deploy any changes you push.
Pause - I'll come back to this later
Shut down the cluster to free up memory while preserving your project state for later.
- If you have the kubectl port-forward process still running in a terminal, press Ctrl+C to stop it.
- Stop Docker Desktop. The cluster state is preserved and resumes automatically when you restart Docker.
- Your GitHub repo and local files remain untouched.
Delete - I don't want to use this again
Remove all project resources and start fresh. This is irreversible for the cluster and GitHub repo.
- If you have the kubectl port-forward process still running in a terminal, press Ctrl+C to stop it.
- Delete the k3d cluster (this also removes ArgoCD and all deployed resources) by running:
k3d cluster delete gitops-demo
- Delete your local project files by running:
cd ~/Desktop
rm -rf gitops-demo
- Delete the GitHub repo. Go to your gitops-demo repository on GitHub, click Settings, scroll down to the Danger Zone section, and click Delete this repository. Confirm by typing the repository name when prompted.
That's a Wrap!
That's a Wrap!
Nice work! You just built a fully automated GitOps deployment pipeline using k3d and ArgoCD on your local machine.
You've learned how to:
- Built a complete GitOps loop where a git push automatically deploys to your Kubernetes cluster with no manual kubectl commands.
- Connected ArgoCD to a GitHub repo as the single source of truth, replacing manual deployments with declarative, auditable configuration.
- Experienced the full deployment pattern used by modern platform engineering teams: push a manifest change, watch the cluster reconcile itself, and demo it live.
- Secret Mission: Broke your own cluster on purpose and watched ArgoCD's self-healing automatically revert unauthorized changes back to the desired state declared in Git.
Ready to quiz yourself?