By the end you'll know
- How to point an Application at your own repository, branch and folder
- What a deploy looks like in GitOps: a pull request, merged
- How Argo CD finds out about a change, and why it can take minutes
- How Argo CD's history lines up with
git log - Why Synced doesn't always mean users see the change, with a ConfigMap as the example
Before you start
- You need: Lessons 1 and 2 done, with the
gitopscluster running and theargocdCLI logged in. - Your own copy of the repository: to follow along with your own commits, fork
Kwamib/gitops-practiceon GitHub, clone your fork, and changerepoURLinargocd/bookshop.yamlto your fork's address. Argo CD can read any public repository, so no keys are needed. - Get the new files:
git pull. You'll haveapps/bookshop/andargocd/bookshop.yaml. (If you're starting after Lesson 4, the page is already the full storefront rather than the two lines shown here. Everything else works the same.) - Where commands run: your own computer's terminal, in
gitops-practice.
The short version
- The app. The bookshop, as plain Kubernetes YAML in
apps/bookshop/: a page in a ConfigMap, nginx to serve it, and a Service. - The deploy. One Application points Argo CD at that folder on
main. It was running 10 seconds later. - A change. Two copies, and a new line on the page, through a pull request. Argo CD noticed the merge 265 seconds later, on its own routine check of GitHub.
- The trap. Argo CD said Synced, but the page still showed the old text. A ConfigMap change doesn't restart pods; the file inside them catches up later. Lesson 6 has the fix.
From an example to your own
Lessons 1 and 2 used Argo CD's own example repository. That shows how Argo CD works, but not the part that matters: you change Git, and the cluster changes. For that, Argo CD needs to watch a repository you can commit to. This lesson uses gitops-practice, the same repository the lessons' files are in.
The bookshop, as plain YAML
apps/bookshop/ is the bookshop website, in three files you'd recognize from the Kubernetes track:
apps/bookshop/configmap.yaml
apps/bookshop/deployment.yaml
apps/bookshop/service.yaml
argocd/bookshop.yaml
argocd/guestbook.yaml
The page lives in a ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: bookshop-page
data:
index.html: |
<h1>bookshop</h1>
<p>Deployed by Argo CD, from gitops-practice.</p>
The Deployment runs nginx, pinned to 1.28, and mounts that ConfigMap as nginx's web folder, so index.html becomes the home page:
spec:
replicas: 1
...
containers:
- name: nginx
image: nginx:1.28
ports:
- containerPort: 80
volumeMounts:
- name: page
mountPath: /usr/share/nginx/html
readinessProbe:
httpGet:
path: /
port: 80
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
memory: 64Mi
volumes:
- name: page
configMap:
name: bookshop-page
The readiness check and the resource limits are from the Kubernetes track's health-check and limits lessons. And a Service named bookshop gives it a stable address. There's no namespace in any of the files: the Application decides where they go.
The Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: bookshop
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/Kwamib/gitops-practice.git
targetRevision: main
path: apps/bookshop
destination:
server: https://kubernetes.default.svc
namespace: bookshop
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
The same shape as the guestbook's, with three changes: your repository, targetRevision: main rather than HEAD, so it follows one named branch, and path: apps/bookshop. Everything in that folder, and only that folder, belongs to this app.
The first deploy
The files go to GitHub first. Argo CD reads GitHub, not your laptop, so a file that isn't pushed doesn't exist as far as it's concerned:
git add apps argocd/bookshop.yaml
git commit -m "Lesson 3: the bookshop app, and its Application"
git push origin main
kubectl apply -f argocd/bookshop.yaml
6d00a98 Lesson 3: the bookshop app, and its Application
application.argoproj.io/bookshop created
synced to 6d00a98 and Healthy after 10s
(The last line is the lesson's script, checking every five seconds until Argo CD reports that exact commit, and Healthy.) Ten seconds. A new Application is checked straight away.
argocd app get bookshop
kubectl get pods -n bookshop
Sync Status: Synced to main (6d00a98)
Health Status: Healthy
GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE
ConfigMap bookshop bookshop-page Synced configmap/bookshop-page unchanged
Service bookshop bookshop Synced Healthy service/bookshop unchanged
apps Deployment bookshop bookshop Synced Healthy deployment.apps/bookshop unchanged
NAME READY STATUS RESTARTS AGE
bookshop-8467b4689d-mkm6t 1/1 Running 0 10s
Synced to the commit you just pushed, all three resources there. (The ConfigMap has no health: there's nothing about a ConfigMap that can be unhealthy.) Is it serving? A port-forward to the Service, in its own terminal, then curl:
kubectl port-forward svc/bookshop -n bookshop 8081:80
curl -s http://localhost:8081/
<h1>bookshop</h1>
<p>Deployed by Argo CD, from gitops-practice.</p>
The page from the ConfigMap, served by nginx, deployed from your repository.
A change, the GitOps way
Now a real change: two copies of the bookshop, and a line on the page saying so. Not with kubectl. On a branch, through a pull request, the way a team would:
git switch -c two-copies
# edit replicas: 1 to replicas: 2, and the line on the page
git diff
--- a/apps/bookshop/configmap.yaml
+++ b/apps/bookshop/configmap.yaml
@@ -6,4 +6,4 @@ metadata:
data:
index.html: |
<h1>bookshop</h1>
- <p>Deployed by Argo CD, from gitops-practice.</p>
+ <p>Deployed by Argo CD, from gitops-practice. Now with two copies.</p>
--- a/apps/bookshop/deployment.yaml
+++ b/apps/bookshop/deployment.yaml
@@ -6,7 +6,7 @@ metadata:
labels:
app: bookshop
spec:
- replicas: 1
+ replicas: 2
selector:
matchLabels:
app: bookshop
git commit -am "Bookshop: two copies, and say so on the page"
git push -u origin two-copies
gh pr create --fill --base main
gh pr merge --squash --delete-branch
git switch main && git pull
git log --oneline -3
https://github.com/Kwamib/gitops-practice/pull/2
c745b1f Bookshop: two copies, and say so on the page (#2)
6d00a98 Lesson 3: the bookshop app, and its Application
1be1855 Lesson 2: ignore the local Argo CD admin password file (#1)
That pull request is the deploy. Someone could have reviewed it, and the change has an author, a time and a reason. Nobody ran anything against the cluster. Then the wait:
synced to c745b1f and Healthy after 265s
265 seconds: about four and a half minutes. Nothing told Argo CD that the pull request was merged. It checks each Application's repository on a timer, every few minutes by default, and found the new commit on one of those checks. The timer is a setting, timeout.reconciliation in the argocd-cm ConfigMap:
kubectl get configmap argocd-cm -n argocd -o jsonpath="{.data.timeout\.reconciliation}"; echo " (blank means the default)"
(blank means the default)
Blank: the default. Two ways to be faster. argocd app get bookshop --refresh tells Argo CD to check now, which is handy when you're watching. And in real setups, a webhook: GitHub calls Argo CD the moment something is pushed, and the change arrives in seconds. That needs GitHub to be able to reach Argo CD, which a cluster on your laptop isn't, so here the timer it is.
Every deploy is a commit
argocd app history bookshop
git log --oneline -3
ID DATE REVISION
0 2026-11-03 19:17:20 -0500 EST main (6d00a98)
1 2026-11-03 19:22:03 -0500 EST main (c745b1f)
c745b1f Bookshop: two copies, and say so on the page (#2)
6d00a98 Lesson 3: the bookshop app, and its Application
1be1855 Lesson 2: ignore the local Argo CD admin password file (#1)
Argo CD's two deploys are Git's two commits. "What changed in the last deploy, and who did it?" is git show c745b1f. "What was running on Tuesday?" is whatever commit was on main on Tuesday. Lesson 9 uses this to roll back.
Synced, but not seen
After that sync, the pods:
NAME READY STATUS RESTARTS AGE
bookshop-8467b4689d-fdb9p 1/1 Running 0 5s
bookshop-8467b4689d-mkm6t 1/1 Running 0 4m48s
Two copies, as Git says. And the page:
<h1>bookshop</h1>
<p>Deployed by Argo CD, from gitops-practice.</p>
The old text. Argo CD said Synced and Healthy, Git had the new line, and the page didn't. Look at the pod names. Both start bookshop-8467b4689d: the same ReplicaSet, from the Kubernetes track's Deployments lesson. Changing replicas only adds a copy; it doesn't replace any. And changing a ConfigMap doesn't restart anything at all. The old pod, mkm6t, kept running, with the page mounted as a file. Kubernetes does update a mounted ConfigMap's files in place, but only after a delay, and the port-forward sends curl to a single pod. It reached the old one before its file had caught up.
Checked later, everything had arrived:
kubectl get configmap bookshop-page -n bookshop -o jsonpath='{.data.index\.html}'
for p in $(kubectl get pods -n bookshop -o name); do echo "== $p"; kubectl exec -n bookshop $p -- cat /usr/share/nginx/html/index.html; done
<h1>bookshop</h1>
<p>Deployed by Argo CD, from gitops-practice. Now with two copies.</p>
== pod/bookshop-8467b4689d-fdb9p
<h1>bookshop</h1>
<p>Deployed by Argo CD, from gitops-practice. Now with two copies.</p>
== pod/bookshop-8467b4689d-mkm6t
<h1>bookshop</h1>
<p>Deployed by Argo CD, from gitops-practice. Now with two copies.</p>
The ConfigMap, and both pods' copies of the file, all with the new line, and neither pod restarted. Synced means the cluster's objects match Git. It doesn't promise every running program has picked the change up. For a page served straight from a file, the delay is short. For an app that reads its settings once, at startup, or from environment variables, it never picks the change up until the pod restarts. The standard fix, a checksum of the ConfigMap in the pod template so that a settings change rolls the pods, comes with Helm in Lesson 6.
In real systems
- Webhooks, not waiting. Teams set up a webhook from GitHub or GitLab to Argo CD, so a merge is deployed in seconds. The timer stays as a safety net, in case a webhook is ever missed.
- The app's repository and the GitOps repository. Here the YAML and the Application share a repository. Many teams keep each app's code in its own repository, and what runs where in a separate GitOps repository: a deploy is then a small change there, often made by the pipeline. Lesson 7 builds that split.
- Branch protection makes it safe. If merging to
maindeploys, thenmainneeds protecting: required reviews, and the lint checks from the Ansible track. Otherwise anyone who can push can deploy. - Settings that need a restart. Every team meets "the ConfigMap changed and nothing happened" eventually. Helm's checksum annotation, or a tool that restarts pods when their ConfigMaps change, are the usual answers.
Spot the problem
Work out what's wrong before you read the explanation.
The fix that never left the laptop. An engineer changes the bookshop's page, commits, and runs argocd app get bookshop --refresh. It says Synced, to the same commit as before, and the page hasn't changed. They refresh again, wait ten minutes, then restart the pods by hand. Still the old page. They decide Argo CD must be broken. What's wrong? The commit was never pushed. git commit saved it on their laptop; Argo CD reads GitHub. As far as Argo CD knows, nothing has changed, so Synced to the old commit is exactly right. git status would have said "Your branch is ahead of 'origin/main' by 1 commit". The fix is git push. And restarting the pods by hand changed nothing, because the cluster already matched what Argo CD could see.
Keep this
| To | Run or write |
|---|---|
| Point an Application at your repository | repoURL:, targetRevision: main, path: apps/bookshop |
| Add the Application | kubectl apply -f argocd/bookshop.yaml |
| Deploy a change | A branch, a commit, a pull request, merged |
| Make Argo CD check Git now | argocd app get bookshop --refresh |
| See how often it checks | timeout.reconciliation in the argocd-cm ConfigMap; blank is the default |
| Which commit is running | argocd app get bookshop: Synced to main (COMMIT) |
| Every deploy, with its commit | argocd app history bookshop, next to git log --oneline |
| Reach the app | kubectl port-forward svc/bookshop -n bookshop 8081:80 |
| See a file inside each pod | kubectl exec -n bookshop POD -- cat FILE |
| Check your commit reached GitHub | git status: no "ahead of origin" |
Where you are now
The bookshop runs on your cluster from your own repository, and changes to it arrive by pull request, on Argo CD's timer. Every deploy is a commit, and you've seen that Synced and "users see it" aren't always the same moment. Lesson 4 looks closer at sync itself: what prune and selfHeal really do, what happens when you delete a file from Git, and when you'd want a person to press sync instead.
Check yourself
Where does Argo CD read the bookshop's YAML from?
The pull request was merged. Why did the change take 265 seconds to arrive?
How do you deploy a change to the bookshop?
After the sync, the page still showed the old text. Why?
Both pods were named bookshop-8467b4689d-something. What does that tell you?
Argo CD says Synced, to the old commit, after you committed a fix. What's the likeliest cause?
Key terms
- targetRevision: the branch, tag or commit an Application follows, like
main. - path: the folder in the repository that holds an Application's YAML.
- Reconciliation timer: how often Argo CD checks Git for changes when nothing tells it;
timeout.reconciliation. - Refresh: asking Argo CD to check Git now, rather than on its next timed check.
- Webhook: a call from GitHub to Argo CD the moment something is pushed.
- ConfigMap: settings or files for pods, kept in the cluster; mounted files update in place, after a delay.
- ReplicaSet: the set of identical pods a Deployment keeps running; a new one appears only when the pod template changes.
Go deeper
- The Argo CD documentation: Automated sync policy (opens in a new tab), and Git webhook configuration (opens in a new tab).
- Try: change only the page, merge it, and time how long Argo CD takes again. Is it the same? Then make another change and run
argocd app get bookshop --refreshstraight after merging. - Try: add an environment variable to the Deployment from the ConfigMap, with
envFrom, change the ConfigMap, and check the variable inside a running pod withkubectl exec ... -- env. Does it ever update without a restart?