Think of this repository as your classroom lab. You will:
- Package a FastAPI service with Helm.
- Let Argo CD keep a Kind cluster in sync with your Git history.
- Prove the CI/CD story end-to-end, including local GitHub Actions runs via
act.
Throughout this guide substitute YOUR_GITHUB_USERNAME wherever you see that placeholder (for example, the GHCR repo path).
| Tool | Why you need it | Quick check |
|---|---|---|
| Docker | Builds/pushes images, backs Kind | docker ps |
| Kind | Spins up the Kubernetes lab env | kind version |
| kubectl | Talks to the cluster | kubectl version --client |
| Helm | Renders our chart | helm version |
| Act | Replays GitHub Actions locally | act --version |
| Git | Version control | git --version |
Install missing tools (Homebrew/Chocolatey or your distro’s package manager work). Start Docker Desktop before you move on.
If you have not cloned the code yet, run:
git clone <repository-url>
cd local-gitops-pipelineWorking on the FastAPI app outside of Docker? Create a virtual environment and install dependencies:
python3 -m venv .venv source .venv/bin/activate pip install -r app/requirements.txt uvicorn app.main:app --reload
File → Open Folder → local-gitops-pipeline
Open the VS Code terminal (`Ctrl + ``). Every command below assumes you run it from the repository root.
bash scripts/setup_kind.shThe script:
- Ensures a Kind cluster named
gitops-labexists. - Installs the official Argo CD manifests into the
argocdnamespace. - Waits until every Argo component is Ready.
Need a different cluster name or Argo version?
CLUSTER_NAME=dev-gitops ARGOCD_VERSION=v2.11.3 bash scripts/setup_kind.shkubectl port-forward svc/argocd-server -n argocd 8080:443Open http://localhost:8080 and sign in:
-
Username:
admin -
Password:
kubectl -n argocd get secret argocd-initial-admin-secret \ -o jsonpath="{.data.password}" | base64 -d && echo
Leave this port-forward running if you want to monitor syncs.
docker compose up -d
docker build -t localhost:5000/myapp:latest ./app
docker push localhost:5000/myapp:latestEdit helm/myapp/values.yaml so it points to the registry you just used:
image:
repository: localhost:5000/myapp
tag: latest
digest: ""Need to stay completely offline? Run
scripts/load-image-into-kind.sh. It loads the freshly built image straight into Kind so Kubernetes never tries to reach a registry (make sureimage.digestremains empty).
kubectl apply -k manifests/This creates the gitops namespace and an Argo CD Application pointing to helm/myapp in this repository (main branch). You should now see myapp listed in the Argo CD UI.
In the Argo CD dashboard: myapp → Sync. Argo will clone this repo, render the Helm chart, and apply it to the gitops namespace.
Watch the rollout from the terminal:
kubectl get pods -n gitopsYou are looking for READY 1/1 and STATUS Running.
The service name follows the <release>-<chart> pattern (myapp-myapp). Port-forward it:
kubectl port-forward svc/myapp-myapp -n gitops 8000:8000
curl http://localhost:8000Expected response:
{"message":"Hello from <pod-name> - GitOps works!"}GitHub’s hosted runners already have everything we need, but running the workflow locally requires a compatible image. Build it once:
docker buildx build --platform linux/amd64 -t local/act-ubuntu:amd64 \
-f docker/act-runner/Dockerfile . --loadThen run:
act push --job build --container-architecture linux/amd64 \
-P ubuntu-latest=local/act-ubuntu:amd64 --pull=falseWhat happens:
- Docker image build and smoke test (FastAPI container must respond on port 8000).
helm lintvalidation of the chart.- No GHCR publishing locally (the workflow short-circuits when the actor is
nektos/act), but on GitHub the workflow builds multi-arch images, pushes toghcr.io/YOUR_GH_USERNAME/myapp, updateshelm/myapp/values.yamlwith the new tag + digest, and opens a pull request.
- Modify
app/main.py(change the greeting text, add a new endpoint—anything visible). - Rebuild/push the image (or run
scripts/load-image-into-kind.sh). - Wait ~60 seconds. Argo CD sees the new artifact, rolls out a fresh Deployment, and your curl output updates automatically.
Q: Do I have to use localhost:5000?
A: No. Point helm/myapp/values.yaml to any registry you control (Docker Hub, GHCR, Harbor, etc.). Just remember to push the image there and, if needed, configure imagePullSecrets.
Q: Where do I set YOUR_GITHUB_USERNAME?
A: Search the repo for that placeholder (values file, manifests, README). Replace it with your actual account before committing.
Q: Can I skip Kind and run this on another cluster?
A: Yes, as long as kubectl context points to a cluster where you have admin rights. Reuse the Argo CD install steps, but be sure to update any load balancer or ingress settings to match your environment.
Q: Why does act insist on linux/amd64?
A: GitHub-hosted runners for Ubuntu jobs are amd64. Matching that architecture locally avoids surprises with multi-arch Docker builds and platform-specific binaries.
| Symptom | Likely cause | Fix |
|---|---|---|
ErrImagePull / ImagePullBackOff |
Cluster cannot reach your registry | Push to GHCR/Docker Hub, allow insecure registry in Kind config, or run scripts/load-image-into-kind.sh. |
Argo CD SyncError: namespace "gitops" not found |
You deleted the namespace | Re-run kubectl apply -k manifests/. |
Port-forward fails with service "myapp" not found |
Helm named the service myapp-myapp |
Use kubectl port-forward svc/myapp-myapp -n gitops 8000:8000. |
act complains about missing runner image |
Build the provided runner (docker/act-runner/Dockerfile) and pass -P ubuntu-latest=local/act-ubuntu:amd64 --pull=false. |
|
| GHCR rejects pushes with 403 | Package is private and the cluster lacks credentials | Either make the GHCR package public or create an imagePullSecret in the gitops namespace. |
Helpful reset commands:
kind delete cluster --name gitops-lab
kubectl delete application myapp -n argocd --ignore-not-found
kubectl delete namespace gitops --ignore-not-found# Build + push app
docker build -t localhost:5000/myapp:latest ./app
docker push localhost:5000/myapp:latest
# Load image into Kind instead of pushing
scripts/load-image-into-kind.sh
# Apply Argo resources
kubectl apply -k manifests/
# Watch resources
kubectl get app -n argocd
kubectl get pods -n gitops -w- Wire Argo CD notifications (Slack, Teams, or Discord).
- Add Prometheus + Grafana via Helm for visibility.
- Create
manifests/dev,manifests/staging,manifests/prodoverlays and drive environment promotion with Git branches. - Extend
.github/workflows/ci.ymlwith Trivy, Hadolint, or policy checks. - Build a
.devcontainer/so new contributors land in a fully provisioned environment.
You now have a complete local GitOps playground—experiment freely and teach others how Git, Kubernetes, and CI/CD fit together. Happy shipping!