Build a GitOps Status Page with Argo CD
Deploy a GitOps status page and use Argo CD to repair Kubernetes drift.
Introduction
30 Second Summary
Saving the latest instructions in one place does not guarantee that a running app follows them. A small manual change can also pull the live version away from what everyone agreed to ship.
In this project, you will build a browser-visible status page on Kubernetes from manifests stored in GitHub. You will use Argo CD to keep the live cluster matched to Git automatically.
What You'll Build
At http://localhost:8081, you'll watch a manual version 1 page become an automatic version 3 release as Argo CD reports the application Synced and Healthy.
By the end of this project, you'll have:
- A status page you can refresh as it moves from Version 1: deployed manually to Version 3: delivered automatically by Argo CD.
- A Git-driven release workflow where a pushed commit becomes the live page with no manual synchronization command.
- Visible proof of configuration drift as a direct scale change leaves one replica running. You will see self-healing restore the declared count of two.
- Secret Mission: Enable automatic pruning so deleting the tracked release-notes ConfigMap from Git removes the orphaned live resource.
Are there any prerequisites?
The project assumes macOS with a running Docker engine. Minikube must already use the Docker driver.
You also need kubectl, Git, Homebrew, an installed IDE, plus a GitHub account that can create public repositories.
Before We Start
This is your moment to commit to building a Git-backed Kubernetes status page. You will also define why Argo CD should reconcile Git's desired state with Minikube's live state before any hands-on work begins.
Install Argo CD on Minikube
Your Git-backed status page needs a reachable Kubernetes control plane before a reconciler can compare desired state with live resources.
Argo CD uses a local CLI to communicate with controllers inside Minikube. Both sides need version 3.5.2 so your terminal matches the installation running in the cluster.
In this step, get ready to:
- Confirm the Docker-backed Minikube cluster is running.
- Install the Argo CD CLI through Homebrew.
- Access the Argo CD UI through a local port-forward.
Confirm the local cluster
Docker provides the runtime for your local Minikube cluster. The cluster must also be reachable through kubectl before Argo CD can manage anything inside it.
- Confirm the complete local cluster chain by running these commands:
docker version
minikube status
kubectl get po -A
What do these checks prove?
- The docker check confirms that the container engine can answer requests.
- The minikube check reports whether your local cluster is running.
- The kubectl check proves that your current context can read workloads from the cluster.
✔️ Minikube is running
Good. Docker is reachable through Minikube, while kubectl can read the cluster that Argo CD is about to manage.
ⓧ Minikube is stopped
Your existing Minikube profile still holds its configuration. Starting it with the Docker driver restores the local control plane.
- Start the existing Minikube cluster by running this command:
minikube start --driver=docker
What does this command do?
Minikube starts the local cluster with Docker as its driver. Your existing profile becomes available to kubectl again.
- Confirm the cluster is running by rerunning this command:
minikube status
What should you see?
Minikube should report that the cluster services are running. This confirms that the control plane is ready for Argo CD.
Minikube still not running?
Check that your Docker engine has finished starting before you retry the Minikube command.
If the profile still cannot start, use the error from your terminal to diagnose the Minikube startup problem.
ⓧ Docker is unavailable
Minikube cannot start with its configured driver until your existing Docker engine is running.
- Press Cmd+Space to open Spotlight on your Mac.
- Type Docker into Spotlight.
- Press Enter to start the Docker app.
- Return to the Terminal window from earlier once the Docker engine reports that it is running.
- Confirm the Docker engine can answer requests by running this command:
docker version
What should you see?
The command should return details from the local Docker engine. That response means Minikube can use the Docker driver.
- Start your existing Minikube cluster by running this command:
minikube start --driver=docker
What does this command do?
Minikube starts the local cluster through the Docker engine you just confirmed. The command also reconnects kubectl to that profile.
Docker still unavailable?
Wait for the Docker engine to finish starting before retrying the checks.
If the terminal still cannot reach Docker, use its output to troubleshoot the local Docker engine.
Install the CLI and controllers
The Argo CD CLI runs on your Mac. The Argo CD controllers run inside the cluster, where they can compare future Git manifests with live Kubernetes resources.
- Check whether the required Argo CD CLI is already available by running this command:
argocd version --client
What does this command do?
The command prints the local client version without connecting to an Argo CD server. Use the result to choose the matching path below.
✔️ I see the required client version
Your local CLI reports version 3.5.2. That client is ready to communicate with the matching controllers.
ⓧ I see an older version
The installed formula needs the current Argo CD release before you continue.
- Update the installed Argo CD formula by running this command:
brew install argocd
What does this command do?
Homebrew installs the current Argo CD formula. An outdated installation is updated through the same formula.
- Verify the updated client version by running this command:
argocd version --client
What should you see?
The client output should now report version 3.5.2. Your local CLI now matches the release used by the cluster installation.
Homebrew could not update Argo CD?
Check that Homebrew can access the network before retrying the installation.
Use the command output to diagnose the Homebrew installation.
ⓧ Command not found
The missing command confirms that the Argo CD CLI still needs to be installed on your Mac.
- Install the Argo CD CLI through Homebrew by running this command:
brew install argocd
What does this command do?
Homebrew downloads the Argo CD formula and makes its CLI available from your terminal.
- Verify the installed client version by running this command:
argocd version --client
What should you see?
The client output should report version 3.5.2. The local half of your Argo CD setup is ready.
Argo CD command still missing?
Close your current Terminal window after Homebrew finishes. Start a new Terminal window so the updated command path is loaded.
If the command remains unavailable, use your Homebrew output to find why the Argo CD CLI is missing.
The CLI is ready. The next installation places Argo CD's non-HA controllers inside a dedicated namespace in Minikube.
- Create the Argo CD namespace and apply the pinned installation by running these commands:
kubectl create namespace argocd
kubectl apply -n argocd --server-side --force-conflicts -f https://raw.githubusercontent.com/argoproj/argo-cd/v3.5.2/manifests/install.yaml
What does this installation create?
The first command creates an isolated namespace for Argo CD. The second command installs the version 3.5.2 non-HA components from the pinned manifest.
The non-HA installation fits local demonstrations and testing. It gives this project the controllers needed for GitOps reconciliation.
The first startup can take a few minutes while Minikube downloads the container images. Quiet terminal output during that wait does not mean the installation has stalled.
- Inspect the Argo CD workloads by running this command:
kubectl get pods -n argocd
What should you see?
You should see the Argo CD pods in the dedicated namespace. Rerun the command until each workload reports that it is running.
Pods not reaching a running state?
Give Minikube another minute to download the images before rerunning the pod check.
If a pod keeps restarting or remains pending, use the pod list to diagnose the Argo CD workload.
That is the cluster-side installation complete. Your Mac now has the matching CLI, while Minikube has the Argo CD controllers.
Connect to the Argo CD UI
Core mode connects the CLI directly through your current Kubernetes context. Browser access uses a separate local port-forward to the Argo CD server service.
- Configure the CLI to use your Minikube cluster directly by running this command:
argocd login --core
What does core mode do?
Core mode uses your current Kubernetes access instead of asking the CLI to connect through the Argo CD API server. This keeps the local CLI tied to the Minikube context you already verified.
Core login not connecting?
Confirm that kubectl still points to the running Minikube cluster before retrying core mode.
Use the login output to troubleshoot the Argo CD core connection.
The next command reveals the initial administrator password in your terminal. Keep that credential private and avoid capturing it in a screenshot.
- Retrieve the initial administrator password by running this command:
argocd admin initial-password -n argocd
How is this password used?
The command reads the initial password created for the local Argo CD installation. You use it once to sign into the browser UI as admin.
- Press Cmd+N in the Terminal app from earlier to create a dedicated window for the port-forward.
- Start the Argo CD server port-forward in that dedicated window by running this command:
kubectl port-forward svc/argocd-server -n argocd 8080:443
What does this port-forward do?
The command maps local port 8080 to port 443 on the Argo CD server service. Keep this Terminal window open because the forwarding process stays active.
Press Control-C in that window when you want to stop browser access.
Browser cannot reach Argo CD?
Check that the dedicated Terminal window still contains the active port-forward process. Restart the command if that process was stopped.
If the port is unavailable, use the terminal output to diagnose the Argo CD port-forward.
Before you run the final check, consider whether the terminal and browser will confirm the same Argo CD installation.
- Verify the local client one final time by running this command in your first Terminal window:
argocd version --client
What should you see?
The client output should report version 3.5.2. This confirms that your local CLI matches the pinned cluster installation.
- Visit https://localhost:8080 in your browser.
- Continue past the local self-signed certificate warning.
You should see the Argo CD login page. That page proves the active port-forward can reach the server inside Minikube.
- Enter admin in the username field.
- Paste the initial password from your first Terminal window into the password field.
- Submit the login form.
You are now signed into the Argo CD UI. The local control plane is ready to take ownership of the status page later in the project.
Argo CD is running on Minikube with matching terminal access and browser access. Next, you will deploy the first visible version of your status page without GitOps.
Deploy the Status Page Without GitOps
Your Argo CD control plane is ready. It does not have an application to compare yet.
A repository and a running cluster can hold different versions of the same desired state. This step tests whether a Git commit alone changes a workload that you deployed manually.
In this step, get ready to:
- Create a public GitHub repository containing five Kubernetes manifests.
- Deploy version 1 of the status page manually to Minikube.
- Commit version 2 to Git and test whether the cluster follows it.
Create the repository and manifests
The repository stores the desired state as YAML files. The first commit declares version 1 so you can deploy a known baseline.
- Sign in to GitHub in your browser.
- Select New repository.
- Enter argocd-gitops-status in the repository name field.
- Choose Public for the repository visibility.
- Leave the repository initialization options empty.
- Click Create repository.
- Select HTTPS on the repository page.
- Record the copied repository URL here: https://github.com/your-github-username/argocd-gitops-status.git.
Your empty public repository is ready to receive the manifests.
- Move to your Desktop in the Terminal window you use for project commands by running:
cd ~/Desktop
What does this command do?
This changes the Terminal location to your Desktop. The cloned repository is easy to find there.
- Clone the public repository over HTTPS by running:
git clone [[YOUR_REPO_URL="https://github.com/your-github-username/argocd-gitops-status.git"]]
cd argocd-gitops-status
What does this command do?
The first command copies the repository to your Desktop. The second command moves the Terminal into the new argocd-gitops-status folder.
- Confirm that the cloned repository appears on your Desktop.
Clone did not complete?
Check that the repository URL ends with argocd-gitops-status.git. Confirm that the repository is public.
If Git asks for credentials or reports an access problem, help me troubleshoot cloning my public GitHub repository over HTTPS..
- Create the manifest files inside the cloned repository by running:
mkdir manifests
touch manifests/00-namespace.yaml
touch manifests/10-page-configmap.yaml
touch manifests/20-deployment.yaml
touch manifests/30-service.yaml
touch manifests/40-feature-flag.yaml
What does this command do?
The folder keeps the workload definitions together. The numbered file names preserve a clear reading order.
- Confirm that all five files exist by running:
ls manifests
What should you see?
The output lists the five numbered YAML files. This confirms that each manifest has a place before you add its configuration.
- Open the argocd-gitops-status folder from your Desktop in your installed IDE.
- Paste this namespace definition into manifests/00-namespace.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: gitops-demo
What does this manifest do?
The Namespace creates an isolated area named gitops-demo. Every status page resource uses that namespace.
- Save manifests/00-namespace.yaml.
- Validate the namespace manifest without changing the cluster by running:
kubectl apply -f manifests/00-namespace.yaml --dry-run=client
Dry run reports a problem?
This command checks the manifest locally without creating the Namespace. A successful result identifies the Namespace as a dry run.
If the check stops, compare the indentation beneath metadata with the snippet above. You can also ask for help with the namespace YAML..
- Paste this initial version of the page into manifests/10-page-configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: gitops-status-page
namespace: gitops-demo
data:
index.html: |
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GitOps Status</title>
</head>
<body>
<main>
<p class="status">Synced from Git</p>
<h1>Version 1: deployed manually</h1>
<p>The repository is the desired state. Argo CD continuously compares it with Kubernetes.</p>
<p>Application: <code>my-app</code></p>
</main>
</body>
</html>
What does this manifest do?
The ConfigMap stores the page as an index.html value. Its heading identifies the version that reaches the cluster.
- Save manifests/10-page-configmap.yaml.
- Validate the initial page manifest by running:
kubectl apply -f manifests/10-page-configmap.yaml --dry-run=client
Page manifest does not validate?
The dry run checks the ConfigMap without storing it in the cluster. Keep every HTML line indented beneath index.html: |.
If the YAML parser rejects the file, help me check the ConfigMap indentation..
- In manifests/10-page-configmap.yaml find this section:
<title>GitOps Status</title>
</head>
Why find these lines?
These lines mark the end of the page metadata. The first style rules belong between the title and the closing head tag.
- Replace that section with this styled version:
<title>GitOps Status</title>
<style>
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
background: #111827;
color: #f9fafb;
font-family: system-ui, sans-serif;
}
main {
width: min(680px, 88vw);
padding: 3rem;
border: 1px solid #374151;
border-radius: 1rem;
background: #1f2937;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.35);
}
</style>
</head>
What do these styles change?
The body centers the page against a dark background. The main panel adds spacing plus a bordered card around the status content.
- Save manifests/10-page-configmap.yaml.
- Validate the updated page manifest by running:
kubectl apply -f manifests/10-page-configmap.yaml --dry-run=client
Style update breaks the dry run?
The command validates the complete ConfigMap after the first CSS addition. Confirm that <style> and </style> stay aligned beneath the HTML head.
If the YAML no longer validates, help me check the CSS block indentation inside the ConfigMap..
- In the same file find the closing lines of the style block:
main {
width: min(680px, 88vw);
padding: 3rem;
border: 1px solid #374151;
border-radius: 1rem;
background: #1f2937;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.35);
}
</style>
Why find this block?
The closing style tag is the insertion point for the remaining selectors. Keeping them inside the same style block makes the browser apply every rule.
- Replace those lines with this completed style block:
main {
width: min(680px, 88vw);
padding: 3rem;
border: 1px solid #374151;
border-radius: 1rem;
background: #1f2937;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.35);
}
.status {
color: #34d399;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
h1 {
margin-bottom: 0.75rem;
}
code {
color: #93c5fd;
}
</style>
What do these styles change?
The status label becomes green uppercase text. The remaining rules space the heading and highlight the application name.
- Save manifests/10-page-configmap.yaml.
- Validate the completed page manifest by running:
kubectl apply -f manifests/10-page-configmap.yaml --dry-run=client
Completed page still fails validation?
The dry run now checks the complete version 1 page. Confirm that each CSS selector remains inside the style tags.
If the check reports malformed YAML, help me compare my completed page ConfigMap with the expected structure..
- Paste this initial Deployment into manifests/20-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitops-status
namespace: gitops-demo
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: gitops-status
template:
metadata:
labels:
app.kubernetes.io/name: gitops-status
annotations:
demo.nextwork.ai/release: "v1"
spec:
containers:
- name: nginx
image: nginx:1.30.4-alpine
ports:
- name: http
containerPort: 80
What does this manifest do?
The Deployment asks Kubernetes to keep two Pods running from the NGINX image. The v1 annotation records the release represented by the Pod template.
- Save manifests/20-deployment.yaml.
- Validate the initial Deployment by running:
kubectl apply -f manifests/20-deployment.yaml --dry-run=client
Deployment does not validate?
The dry run checks the selector plus the Pod template without creating Pods. Confirm that both app.kubernetes.io/name values match exactly.
If validation stops, help me inspect the Deployment selector and labels..
- In manifests/20-deployment.yaml find the container port section:
ports:
- name: http
containerPort: 80
Why find these lines?
The port section is the anchor for the ConfigMap mount. The mount makes the page file available to the web server.
- Replace that section with the completed container and volume configuration:
ports:
- name: http
containerPort: 80
volumeMounts:
- name: status-page
mountPath: /usr/share/nginx/html
readOnly: true
volumes:
- name: status-page
configMap:
name: gitops-status-page
What does this configuration do?
The volume reads the gitops-status-page ConfigMap. The mount places its index.html file in the directory served by NGINX.
- Save manifests/20-deployment.yaml.
- Validate the completed Deployment by running:
kubectl apply -f manifests/20-deployment.yaml --dry-run=client
Volume configuration does not validate?
The dry run now checks the container plus the ConfigMap volume. Keep volumes aligned beneath the Pod spec.
If the check fails, help me correct the volume and volumeMount indentation..
- Paste this Service into manifests/30-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: gitops-status
namespace: gitops-demo
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: gitops-status
ports:
- name: http
protocol: TCP
port: 80
targetPort: http
What does this manifest do?
The Service selects the status page Pods by label. It forwards port 80 to the named container port.
- Save manifests/30-service.yaml.
- Validate the Service manifest by running:
kubectl apply -f manifests/30-service.yaml --dry-run=client
Service does not validate?
The dry run checks the Service without creating it. Confirm that its selector matches the Deployment label.
If validation stops, help me compare the Service selector with the Deployment label..
- Paste this additional ConfigMap into manifests/40-feature-flag.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: release-notes
namespace: gitops-demo
data:
note: This resource exists so the Secret Mission can demonstrate pruning.
What does this manifest do?
This ConfigMap creates a separate tracked resource named release-notes. It stays independent from the page content.
- Save manifests/40-feature-flag.yaml.
- Validate the additional ConfigMap by running:
kubectl apply -f manifests/40-feature-flag.yaml --dry-run=client
Additional ConfigMap does not validate?
The dry run checks the final manifest without creating the resource. Keep the note indented beneath data.
If it fails, help me check the release-notes ConfigMap structure..
- Commit version 1 to the public repository by running:
git add .
git commit -m "Deploy version 1 manually"
git branch -M main
git push -u origin main
What do these commands do?
- The first command stages all five manifests.
- The second command records the version 1 snapshot.
- The third command names the branch main.
- The final command publishes the branch and configures its upstream.
- Return to the repository page in GitHub.
- Confirm that the manifests directory contains five files.
Good progress. Your public repository now records the exact version you are about to deploy.
Initial push did not complete?
Confirm that the Terminal is inside the cloned argocd-gitops-status folder. Check that your GitHub account has permission to write to the repository.
If Git reports an authentication or branch problem, help me troubleshoot the first push to my public repository..
Deploy version 1 manually
The files in Git describe a workload. A manual apply is the action that places this first version into Minikube.
- Create the gitops-demo Namespace by running:
kubectl apply -f manifests/00-namespace.yaml
What does this command do?
This submits the Namespace manifest to the connected Minikube cluster. The remaining resources now have a destination.
- Create the status page resources by running:
kubectl apply -f manifests/
What does this command do?
This applies every YAML file in the manifests directory. Kubernetes creates the ConfigMaps plus the Deployment and Service.
- Wait for the status page Deployment to become available by running:
kubectl wait --for=condition=available --timeout=120s deployment/gitops-status -n gitops-demo
Deployment does not become available?
The command waits until the Deployment can serve traffic. The first image pull can take a short while on a new Minikube cluster.
If the wait reaches its timeout, help me diagnose why the gitops-status Deployment is unavailable..
- Create a second window in the Terminal app from earlier.
- Move that second Terminal window into the cloned repository by running:
cd ~/Desktop/argocd-gitops-status
Why use another Terminal window?
The service port-forward keeps running while you use the first project Terminal for later Git commands. The Argo CD server port-forward also remains active in its existing window.
- Expose the status page on local port 8081 by running:
kubectl port-forward service/gitops-status -n gitops-demo 8081:80
What does this command do?
The command forwards browser traffic from local port 8081 to the Service on port 80. Keep this Terminal window running while you test the page.
Before you open the page, what version do you expect after manually applying the first commit?
- Open http://localhost:8081 in your browser.
You should see the dark status card with Version 1: deployed manually as its heading.
That is the first visible win. Your manually applied Kubernetes workload is serving the page from Minikube.
Status page does not load?
Keep the service port-forward Terminal running. Confirm that you used local port 8081.
If the browser stays blank or the port-forward stops, help me troubleshoot access to the gitops-status Service..
Commit version 2 without applying it
The browser now proves what the cluster is serving. Next you will change only the repository version so you can compare Git with the live workload.
- In manifests/10-page-configmap.yaml find this heading:
<h1>Version 1: deployed manually</h1>
What does this line represent?
This heading identifies the release stored in the page ConfigMap. It currently matches the version running in the cluster.
- Replace the heading with version 2:
<h1>Version 2: committed to Git</h1>
What changed?
The repository copy now announces version 2. This edit has not changed the ConfigMap already stored in Minikube.
- In manifests/20-deployment.yaml find this annotation:
demo.nextwork.ai/release: "v1"
What does this annotation represent?
The annotation identifies the release attached to the Pod template. Its version currently matches the manually deployed page.
- Replace the annotation with version 2:
demo.nextwork.ai/release: "v2"
Why change the annotation?
A changed Pod template annotation gives each page release a distinct Deployment revision. The repository now declares version 2 in both relevant manifests.
- Save manifests/10-page-configmap.yaml.
- Save manifests/20-deployment.yaml.
- Publish version 2 to GitHub by running:
git add .
git commit -m "Release version 2"
git push
What do these commands do?
The commands stage the two edits and record them as Release version 2. The push updates the configured upstream repository.
- Return to the repository page in GitHub.
- Confirm that the latest commit is Release version 2.
Version 2 push did not complete?
Confirm that the initial push configured the main upstream. Check that the commit command reported two changed files.
If the push is rejected, help me diagnose my version 2 Git push..
Use these tabs to compare your complete version 2 repository before the final test.
✔️ Awesome, I've got everything!
Great. Confirm that both changed files are saved and that the version 2 commit appears on GitHub.
ⓧ I'd like to double check the full code
apiVersion: v1
kind: Namespace
metadata:
name: gitops-demo
Namespace manifest
This file declares the gitops-demo Namespace used by the workload.
apiVersion: v1
kind: ConfigMap
metadata:
name: gitops-status-page
namespace: gitops-demo
data:
index.html: |
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GitOps Status</title>
<style>
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
background: #111827;
color: #f9fafb;
font-family: system-ui, sans-serif;
}
main {
width: min(680px, 88vw);
padding: 3rem;
border: 1px solid #374151;
border-radius: 1rem;
background: #1f2937;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.35);
}
.status {
color: #34d399;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
h1 {
margin-bottom: 0.75rem;
}
code {
color: #93c5fd;
}
</style>
</head>
<body>
<main>
<p class="status">Synced from Git</p>
<h1>Version 2: committed to Git</h1>
<p>The repository is the desired state. Argo CD continuously compares it with Kubernetes.</p>
<p>Application: <code>my-app</code></p>
</main>
</body>
</html>
Page ConfigMap
This file contains the complete styled page. Its repository heading now declares version 2.
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitops-status
namespace: gitops-demo
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: gitops-status
template:
metadata:
labels:
app.kubernetes.io/name: gitops-status
annotations:
demo.nextwork.ai/release: "v2"
spec:
containers:
- name: nginx
image: nginx:1.30.4-alpine
ports:
- name: http
containerPort: 80
volumeMounts:
- name: status-page
mountPath: /usr/share/nginx/html
readOnly: true
volumes:
- name: status-page
configMap:
name: gitops-status-page
Deployment manifest
This file declares two status page replicas. Its Pod template annotation now declares release v2.
apiVersion: v1
kind: Service
metadata:
name: gitops-status
namespace: gitops-demo
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: gitops-status
ports:
- name: http
protocol: TCP
port: 80
targetPort: http
Service manifest
This file routes traffic to Pods carrying the status page label.
apiVersion: v1
kind: ConfigMap
metadata:
name: release-notes
namespace: gitops-demo
data:
note: This resource exists so the Secret Mission can demonstrate pruning.
Additional ConfigMap
This file keeps the release-notes resource tracked beside the status page.
Before you refresh, do you think the browser follows the new Git commit or keeps showing the manually applied version?
- Refresh http://localhost:8081 without running another apply command.
You will still see Version 1: deployed manually. GitHub contains version 2, but the live ConfigMap and Deployment still contain version 1.
Why did the page stay on version 1?
A Git commit records desired state. The cluster needs a reconciliation loop to compare that desired state with its live resources.
This mismatch is intentional. You have proved that storing manifests in Git does not deploy them by itself.
Your repository and cluster now show a real configuration gap. Next, you will connect them through an Argo CD Application and synchronize version 2.
Put Argo CD in Control
Your Git repository now holds version 2. Minikube still serves version 1.
Git defines the desired state. Argo CD needs an Application resource to connect that state to the live Kubernetes cluster.
In this step, get ready to:
- Define my-app as a version-controlled Argo CD Application.
- Observe the difference between Git and the live cluster as OutOfSync.
- Deliver version 2 through a manual Argo CD synchronization.
Create the Argo CD Application
An Application tells Argo CD where the workload manifests live. A YAML file keeps that connection reusable in your repository.
- Use your IDE's file controls to create argocd/application.yaml inside the cloned argocd-gitops-status folder.
- Paste this manifest into argocd/application.yaml:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/your-github-username/argocd-gitops-status.git
targetRevision: HEAD
path: manifests
destination:
server: https://kubernetes.default.svc
namespace: gitops-demo
What does this manifest do?
- The metadata section creates an Application named my-app in the argocd namespace.
- The source section points Argo CD to the manifests directory on the repository's latest revision.
- The destination section targets the same cluster that runs Argo CD.
- The finalizer lets Argo CD remove managed resources when the Application is deleted.
- Save argocd/application.yaml.
- Confirm your IDE's file tree shows application.yaml inside the argocd folder.
Manifest file in the wrong place?
Check that argocd sits directly inside argocd-gitops-status. The file path should be argocd/application.yaml.
YAML depends on consistent spacing. Use spaces for every indentation level.
Help me check my Argo CD Application manifest.
The placeholder in repoURL must match the GitHub account that owns the public repository. This gives Argo CD the exact address of your manifests.
- Locate the repoURL line in argocd/application.yaml.
- Replace your-github-username with your GitHub username.
- Save argocd/application.yaml.
✔️ Awesome, I've got everything!
Your Application manifest is saved with your own GitHub username.
ⓧ I'd like to double check the full code
- Compare the structure of your argocd/application.yaml file with this reference:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/your-github-username/argocd-gitops-status.git
targetRevision: HEAD
path: manifests
destination:
server: https://kubernetes.default.svc
namespace: gitops-demo
How to use this reference
The reference keeps your-github-username as a placeholder. Keep your own GitHub username in that position when comparing the rest of the file.
Argo CD reads the workload manifests from GitHub. The Application file needs a remote commit before you create it in the cluster.
- Stage argocd/application.yaml using your IDE's source control tools.
- Commit the staged file with the message Add Argo CD application.
- Push the commit to GitHub.
- Confirm GitHub lists argocd/application.yaml in the latest commit.
Good. GitHub now holds the reusable Application definition alongside your workload.
Your Git commit stores the Application definition. Applying it creates the my-app resource that Argo CD watches.
- Return to the Terminal session for your cloned argocd-gitops-status repository.
- Create the my-app Application by running this command:
kubectl apply -f argocd/application.yaml
What does this command do?
The command sends argocd/application.yaml to the Kubernetes API. Argo CD can then compare the repository manifests with the resources in gitops-demo.
- Confirm the terminal reports that the Application was created.
Application not created?
Check that your terminal is inside the cloned argocd-gitops-status folder. The relative path only works from that folder.
Confirm the argocd namespace still exists in Minikube.
Help me diagnose my Application creation error.
Observe the OutOfSync gap
The Application now compares Git's version 2 manifests with the cluster's version 1 resources. This comparison exposes the difference between desired state and live state.
Before you refresh the comparison, which version do you think Argo CD treats as the desired state?
- Refresh the Application's repository data by running this command:
argocd app get my-app --hard-refresh
What does the hard refresh do?
A hard refresh asks Argo CD to reload the target manifests from Git. It also refreshes the Application data before displaying the comparison.
You'll see my-app reported as OutOfSync. Git contains version 2 while the cluster still runs version 1.
- Return to the Argo CD UI at https://localhost:8080.
- Open my-app from the application list.
The Application shows OutOfSync. The ConfigMap content and Deployment release annotation differ from Git.
Application missing from the UI?
Refresh the browser page. Confirm the Argo CD server port-forward is still active in its dedicated Terminal window.
Help me find my Application in Argo CD.
Synchronize version 2
A manual synchronization tells Argo CD to make the live resources match Git now. This closes the deployment gap you uncovered.
Before you synchronize, what should happen to the status page when version 2 reaches the cluster?
- Synchronize my-app by running this command:
argocd app sync my-app
What does this command do?
The command applies the Git-defined resources through Argo CD. The live ConfigMap content and Deployment annotation move to version 2.
You'll see the synchronization complete in the terminal. Argo CD now manages the workload at the revision stored in Git.
- Return to the my-app resource tree in the Argo CD UI.
The Application settles at Synced and Healthy.
Before the final check, which two statuses should the CLI report now?
- Refresh the final Application status by running this command:
argocd app get my-app --hard-refresh
What should the final status prove?
The hard refresh confirms that Git and the cluster now match. A healthy result confirms the Deployment is running successfully.
The CLI reports Synced and Healthy for my-app.
- Return to the status page at http://localhost:8081.
- Refresh the browser page.
You'll see Version 2: committed to Git on the status page. Argo CD has replaced the manually deployed version 1 state.
Still seeing version 1?
Confirm the service port-forward is still active for http://localhost:8081. Refresh the browser after my-app reaches Synced.
Check that your GitHub repository contains version 2 in manifests/10-page-configmap.yaml.
Help me diagnose why the status page still shows version 1.
You've closed the missing deployment loop. Version 2 now reaches the cluster through Argo CD.
Argo CD now owns the deployment process. Next, you'll make a Git commit trigger synchronization automatically.
Release Version 3 Through Git
Your Argo CD application is Synced and Healthy after the manual synchronization. The cluster now matches version 2 in Git.
Manual synchronization still depends on someone triggering each deployment. Automated synchronization turns a Git commit into the release trigger without giving another system direct access to your Kubernetes cluster.
In this step, get ready to:
- Enable automated synchronization while keeping pruning and self-healing disabled.
- Publish the version 3 page through a Git commit.
- Watch Argo CD deploy the release without a manual synchronization command.
Enable automated synchronization
An automated synchronization policy tells Argo CD to reconcile new Git revisions without waiting for a manual command. Pruning and self-healing remain disabled so each behavior can be introduced separately.
- In your IDE's Explorer sidebar, select argocd/application.yaml.
- Locate the final namespace: gitops-demo line.
- Add the synchronization policy directly below that line by pasting this block:
syncPolicy:
automated:
enabled: true
prune: false
selfHeal: false
What does this policy do?
- The enabled: true setting allows Argo CD to synchronize a new Git revision automatically.
- The prune: false setting preserves live resources whose manifests disappear from Git.
- The selfHeal: false setting leaves direct changes to live resources in place.
- Save argocd/application.yaml.
- Apply the updated Application policy to Minikube by running this command:
kubectl apply -f argocd/application.yaml
What does this command do?
The command updates the existing my-app Application in the argocd namespace. The controller can now react automatically when the tracked Git revision changes.
You should see confirmation that the existing Application was configured. Version 2 remains Synced because Git has not received the next release yet.
Application policy not updating?
Check that syncPolicy aligns with source and destination under spec. YAML indentation determines which settings belong together.
Confirm that your terminal is still inside the cloned argocd-gitops-status folder.
Need help with the policy file?
Publish version 3 to Git
The next commit changes both the browser-visible heading and the Deployment annotation. The annotation change creates a new Pod template so the release rolls out with the updated page.
- In your IDE's Explorer sidebar, select manifests/10-page-configmap.yaml.
- Find the current heading value Version 2: committed to Git.
- Replace the heading value with Version 3: delivered automatically by Argo CD.
- In your IDE's Explorer sidebar, select manifests/20-deployment.yaml.
- Find the current annotation value demo.nextwork.ai/release: "v2".
- Replace the annotation value with demo.nextwork.ai/release: "v3".
- Use your IDE's Save All command to save the three changed files.
✔️ Awesome, I've got everything!
Your Application policy and version 3 release files are ready to commit.
ⓧ I'd like to double check the full code
- Compare each file with the references below.
- Keep the GitHub username already stored in your own repoURL value.
argocd/application.yaml:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/your-github-username/argocd-gitops-status.git
targetRevision: HEAD
path: manifests
destination:
server: https://kubernetes.default.svc
namespace: gitops-demo
syncPolicy:
automated:
enabled: true
prune: false
selfHeal: false
What should this file contain?
This Application tracks the manifests directory at HEAD. Its synchronization policy enables automatic releases while leaving pruning and self-healing disabled.
manifests/00-namespace.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: gitops-demo
What should this file contain?
This manifest keeps the workload inside the gitops-demo namespace.
manifests/10-page-configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: gitops-status-page
namespace: gitops-demo
data:
index.html: |
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GitOps Status</title>
<style>
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
background: #111827;
color: #f9fafb;
font-family: system-ui, sans-serif;
}
main {
width: min(680px, 88vw);
padding: 3rem;
border: 1px solid #374151;
border-radius: 1rem;
background: #1f2937;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.35);
}
.status {
color: #34d399;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
h1 {
margin-bottom: 0.75rem;
}
code {
color: #93c5fd;
}
</style>
</head>
<body>
<main>
<p class="status">Synced from Git</p>
<h1>Version 3: delivered automatically by Argo CD</h1>
<p>The repository is the desired state. Argo CD continuously compares it with Kubernetes.</p>
<p>Application: <code>my-app</code></p>
</main>
</body>
</html>
What should this file contain?
The ConfigMap stores the complete status page as index.html. Its heading identifies the release that Argo CD delivers automatically.
manifests/20-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: gitops-status
namespace: gitops-demo
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: gitops-status
template:
metadata:
labels:
app.kubernetes.io/name: gitops-status
annotations:
demo.nextwork.ai/release: "v3"
spec:
containers:
- name: nginx
image: nginx:1.30.4-alpine
ports:
- name: http
containerPort: 80
volumeMounts:
- name: status-page
mountPath: /usr/share/nginx/html
readOnly: true
volumes:
- name: status-page
configMap:
name: gitops-status-page
What should this file contain?
The Deployment still declares two replicas. Its v3 annotation creates a new Pod template for this release.
manifests/30-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: gitops-status
namespace: gitops-demo
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: gitops-status
ports:
- name: http
protocol: TCP
port: 80
targetPort: http
What should this file contain?
The Service continues routing port 80 to the status page Pods.
manifests/40-feature-flag.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: release-notes
namespace: gitops-demo
data:
note: This resource exists so the Secret Mission can demonstrate pruning.
What should this file contain?
The release-notes ConfigMap remains part of the desired state for this release.
Git may request GitHub authentication during the push. Complete the authentication flow if it appears.
- Stage the release files, create the version 3 commit, and push it to GitHub by running these commands:
git add .
git commit -m "Enable automated sync and release version 3"
git push
What do these commands do?
- The git add . command stages the Application policy and both release changes.
- The git commit -m "Enable automated sync and release version 3" command records the release in Git history.
- The git push command publishes the commit to the repository tracked by Argo CD.
Good progress. Git now holds the version 3 desired state and its audit history.
Push not reaching GitHub?
Confirm that the commit command reports changes in the three edited files. An empty commit usually means the files were not saved before staging.
Complete any GitHub authentication request shown by Git. Your repository must receive the commit before Argo CD can reconcile it.
Need help with the push?
Watch Argo CD reconcile the release
The pushed commit now differs from the live version 2 workload. Automated synchronization gives Argo CD permission to close that gap by itself.
Before you run the refresh, predict whether the Application will require another manual synchronization command.
- Refresh the repository data and target manifests by running this command:
argocd app get my-app --hard-refresh
What does this command do?
A hard refresh asks Argo CD to retrieve the current repository data and regenerate the target manifests. The automated policy then synchronizes the new revision without argocd app sync my-app.
The synchronization can take a few seconds while Kubernetes replaces the Pods. You should then see my-app report Synced and Healthy.
- Return to the Argo CD UI from earlier.
- Select the my-app application card.
- Confirm that the application reports Synced and Healthy for the new revision.
Before you refresh the status page, predict whether the browser will keep version 2 or display Git's latest release.
- Return to http://localhost:8081 in your browser.
- Refresh the status page.
You should see Version 3: delivered automatically by Argo CD. You reached this state without running a manual synchronization command.
Still seeing version 2?
Confirm that GitHub contains the version 3 commit. Argo CD can only synchronize content that reached the tracked repository.
Check that the Application shows automated synchronization as enabled. Also confirm that the service port-forward from earlier is still active.
Need help tracing the release?
Your Git commit now drives a visible deployment automatically. Next, you will change the live cluster directly and test whether Argo CD repairs the drift.
Create Drift and Let Argo CD Repair It
Your Version 3: delivered automatically by Argo CD release now moves from Git into Kubernetes automatically. Next, you'll test whether the same workflow repairs a change made directly in the cluster.
Automated synchronization in Argo CD reacts to new Git revisions. Live-only changes remain in place while self-healing is disabled.
In this step, get ready to:
- Create live drift by setting the Deployment to one replica.
- Observe Argo CD detect the drift without repairing it.
- Enable self-healing so Argo CD restores the Git-defined replica count.
Create live drift
Git still declares two replicas. Scaling the live Deployment to one creates configuration drift without changing the repository.
- Return to the Terminal window you use for cluster commands.
- Change the live gitops-status Deployment to one replica by running this command:
kubectl scale --replicas=1 deployment/gitops-status -n gitops-demo
What does this command do?
The command changes only the live Deployment to one replica. It leaves manifests/20-deployment.yaml at two replicas.
Before you check the Deployment, do you expect the live replica count to match Git or the scale command?
- Inspect the live Deployment by running this command:
kubectl get deployment gitops-status -n gitops-demo
What should you inspect?
The Deployment summary reflects the live cluster state. Its desired replica count now comes from the direct scale operation.
You'll see that the live Deployment wants one replica. Git still declares replicas: 2.
Deployment still shows two replicas?
- Check that the scale command targets the gitops-demo namespace.
- Repeat the scale command above if another value was entered.
Need help tracing the live replica count?
Observe drift without self-healing
The Application can compare Git with the cluster while leaving the mismatch untouched. This exposes the difference between automated Git synchronization and live-drift repair.
Before you refresh the Application, do you think automated synchronization alone restores the missing replica?
- Refresh the Application comparison and recheck the live Deployment by running these commands:
argocd app get my-app --hard-refresh
kubectl get deployment gitops-status -n gitops-demo
What do these checks prove?
- The hard refresh compares the current Git manifests with live cluster resources.
- The Deployment check confirms whether the live replica count changed.
You'll see Argo CD report OutOfSync. You'll also see that the Deployment still wants one replica.
Application still shows Synced?
- Confirm the Deployment check above still shows one desired replica.
- Check that argocd/application.yaml still contains selfHeal: false.
Need help finding why the drift is missing?
Enable self-healing and verify repair
Self-healing gives Argo CD permission to reconcile live-only drift. The Application can now restore the Deployment to the replica count declared in Git.
- Switch back to argocd/application.yaml in the IDE from earlier.
- Find selfHeal: false under the automated synchronization policy.
- Replace that line with:
selfHeal: true
What changes here?
The selfHeal setting allows automated synchronization to react when live cluster state deviates from Git.
- Save argocd/application.yaml.
✔️ Awesome, I've got everything!
Your Application manifest now enables automated synchronization and self-healing.
ⓧ I'd like to double check the full code
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/your-github-username/argocd-gitops-status.git
targetRevision: HEAD
path: manifests
destination:
server: https://kubernetes.default.svc
namespace: gitops-demo
syncPolicy:
automated:
enabled: true
prune: false
selfHeal: true
What should you compare?
The final synchronization policy keeps pruning disabled. It enables self-healing.
The reference preserves the project placeholder in repoURL. Your working file keeps the GitHub username you entered earlier.
- Apply the updated Application policy to the cluster by running this command:
kubectl apply -f argocd/application.yaml
What does this command change?
The command updates the live my-app Application. Argo CD can now repair the replica drift it already detected.
Application update failed?
- Check that your terminal is inside the argocd-gitops-status folder.
- Confirm that argocd/application.yaml is saved before applying it.
Need help applying the updated Application?
- Stage argocd/application.yaml with the Git workflow from earlier.
- Commit the staged change with a message that describes enabling self-healing.
- Push the commit to your public repository.
- Return to your public GitHub repository page from earlier.
- Confirm the latest commit contains selfHeal: true in argocd/application.yaml.
Your repository now records the same self-healing policy that runs in the cluster.
Before you run the final check, do you expect the Deployment to stay at one replica or return to Git's declared count?
- Check the reconciliation by running these commands. Repeat the check after a few seconds if Argo CD is still processing:
argocd app get my-app --hard-refresh
kubectl get deployment gitops-status -n gitops-demo
What does the final check confirm?
- Argo CD compares the live Deployment with the replica count stored in Git.
- The Deployment summary shows the result of the self-healing reconciliation.
You'll see Argo CD report Synced and Healthy. The Deployment returns to two desired replicas with two available replicas.
Deployment still shows one replica?
- Confirm the applied Application contains selfHeal: true.
- Repeat the final hard refresh after a few seconds.
- Check that Git still declares replicas: 2 in manifests/20-deployment.yaml.
Need help diagnosing the reconciliation?
- Refresh http://localhost:8081 in the browser from earlier.
You'll still see Version 3: delivered automatically by Argo CD. The repair changes capacity without changing the released page.
You have now watched Argo CD detect live drift and restore the Git-defined replica count. Your delivery workflow can enforce its declared state without a manual repair.
Secret mission
Prune a Resource Removed from Git
Remove a tracked ConfigMap from Git while its live resource stays behind. Then enable automatic pruning so Argo CD deletes the orphaned resource from Kubernetes.
Clean Up Your Resources
Clean Up Your Resources
The project uses local Minikube compute plus GitHub Free public repository features. There are no expected service charges.
Your running cluster continues using local computer resources.
Resources you used:
- The my-app Argo CD Application.
- Argo CD components in the argocd namespace.
- The Kubernetes status page workload in the gitops-demo namespace.
- Your existing Minikube profile.
- Two active local port-forward processes.
- The public argocd-gitops-status GitOps repository.
Keep everything running
No action is required. Choose this option if you want to keep demonstrating automated synchronization plus self-healing.
- Leave your existing Minikube profile running.
- Keep the my-app Application active.
- Keep the public argocd-gitops-status repository available.
- Keep both port-forward processes active while you need browser access.
- Press Control-C in each dedicated Terminal window when you finish demonstrating the project.
- Restart the same two port-forward commands from earlier when you return to the demo.
Pause - I'll come back to this later
Pausing frees local computer resources while preserving the cluster state for your return.
- Switch back to the Terminal window running the Argo CD UI port-forward.
- Press Control-C to stop the UI port-forward.
- Switch back to the Terminal window running the status page port-forward.
- Press Control-C to stop the status page port-forward.
- Pause your existing Minikube profile by running this command:
minikube pause
What does this command do?
This pauses the local cluster while keeping its current state available for later.
Your terminal confirms that the Minikube profile is paused.
Cluster Not Pausing?
Make sure your existing Docker engine is still running. Ask for help diagnosing the paused profile.
- Resume the same Minikube profile when you return by running this command:
minikube unpause
How Does Resuming Work?
This resumes the paused cluster with your Argo CD Application plus status page state preserved.
Your terminal confirms that the Minikube profile is running again.
Cluster Not Resuming?
Confirm that the existing Docker engine is running. Ask for help restoring the paused cluster.
Delete - I don't want to use this again
Deleting these project resources is permanent. Your pre-existing Minikube profile stays intact unless you choose the optional cluster deletion below.
- Switch back to the Terminal window running the Argo CD UI port-forward.
- Press Control-C to stop the UI port-forward.
- Switch back to the Terminal window running the status page port-forward.
- Press Control-C to stop the status page port-forward.
The Application deletion command asks for confirmation before it proceeds.
- Delete my-app by running this command:
argocd app delete my-app
What Does This Delete?
The Application finalizer cascade-deletes the managed status page resources from the gitops-demo namespace.
Your terminal confirms that the Application was deleted. The managed workload is removed with it.
Application Not Deleting?
Confirm that your current Kubernetes context still points to the existing Minikube cluster. Ask for help deleting the Application safely.
- Remove the remaining Argo CD components by running this command:
kubectl delete namespace argocd
What Does This Delete?
This removes the argocd namespace plus the Argo CD components inside it.
Your terminal confirms that the argocd namespace was deleted.
Namespace Not Deleting?
Confirm that kubectl is still connected to the existing Minikube cluster. Ask for help removing the namespace safely.
Deleting the Minikube profile also removes any unrelated local workloads stored in that profile. Skip this option unless the profile is dedicated to this project.
- Delete the dedicated Minikube profile by running this command:
minikube delete
What Does This Delete?
This deletes the Minikube profile plus its associated local files.
If you run the command, your terminal confirms that the profile was deleted.
Profile Not Deleting?
Confirm that you are targeting the intended local profile. Ask for help deleting the profile without affecting another environment.
The public repository remains available after the local resources are removed.
- Return to the argocd-gitops-status repository on GitHub.
- Use GitHub's repository deletion flow to remove the repository permanently.
- Enter argocd-gitops-status when GitHub asks you to identify the repository.
- Confirm the repository deletion.
Nice Work!
Nice Work!
Excellent work. Your version 3 status page now follows a GitOps delivery loop from Git through Argo CD into Kubernetes.
What you learned:
- Built a public GitHub repository that stores declarative Kubernetes manifests as the status page's desired state.
- Released the browser-visible version 3 page through automated synchronization from a Git commit.
- Created live configuration drift by scaling the Deployment to one replica. Argo CD self-healing restored the Git-defined count of two.
- Secret Mission: Enabled automatic pruning after removing manifests/40-feature-flag.yaml from Git. Argo CD deleted the orphaned release-notes ConfigMap from the cluster.
Ready to quiz yourself?