This repository demonstrates a complete CI + CD GitOps delivery loop in a home lab environment.
What’s running in the lab right now:
- k3s Kubernetes (3 nodes)
- Argo CD (GitOps deployments)
- Strapi (application) + PostgreSQL (database)
- Prometheus + Grafana (observability)
- NFS dynamic provisioning (PVCs via external provisioner)
- Pi-hole DNS + Nginx Proxy Manager (NPM) on a local Proxmox server for friendly hostnames + reverse proxy
- Traefik is used only as the k3s ServiceLB/LoadBalancer (no Ingress usage in this project)
Goal: No manual kubectl apply / no “deploy from CI”.
The source of truth is Git.
- Change Strapi code (in
strapi/) and push tomain - GitHub Actions CI:
- builds Strapi
- builds & pushes a container image to GHCR
- uses immutable image tag = commit SHA
- opens a PR that updates GitOps desired state in:
gitops/apps/strapi/chart/values.yaml→image.tag: "<sha>"
- Merge the PR
- Argo CD auto-sync detects Git change and rolls out Strapi in k3s
- Verify rollout:
kubectl -n strapi get podskubectl -n strapi describe pod <pod>shows new image tag
- Observability proof:
- Grafana dashboard (CPU/Mem/Restarts/Replicas/Ready Pods)
I don’t use Kubernetes Ingress for this project. Instead:
- Pi-hole provides internal DNS records (
*.apps.home.arpa) - Nginx Proxy Manager (NPM) runs on a local Proxmox server and reverse-proxies to k3s NodePorts
- Example hostnames:
http://strapi.apps.home.arpa/admin→ Strapi NodePort servicehttps://argocd.apps.home.arpa(optional) → Argo CD NodePort service
Traefik is the default k3s ServiceLB and exposes cluster services via:
192.168.0.100,192.168.0.101,192.168.0.102
Traefik is not used for HTTP ingress routing here (NPM handles that externally).
- k3s cluster (1 server + 2 agents)
- Argo CD:
- root app + app-of-apps structure under
gitops/argocd/
- root app + app-of-apps structure under
- Strapi app deployed via Helm chart in Git
- chart & values live under
gitops/apps/strapi/chart/
- chart & values live under
- PostgreSQL deployed via Helm values under
gitops/helm/postgresql/ - Observability
- kube-prometheus-stack values under
gitops/helm/kube-prometheus-stack/ - Grafana dashboard for Strapi is delivered as a ConfigMap with label
grafana_dashboard=1
- kube-prometheus-stack values under
- Dynamic PV provisioning using
nfs-subdir-external-provisioner - Strapi PVCs:
strapi-uploads
- PostgreSQL PVC:
data-strapi-postgresql-0
- dell-optiplex-1 —
192.168.0.100— k3s server (control-plane) - hp-prodesk-1 —
192.168.0.101— k3s agent - hp-prodesk-2 —
192.168.0.102— k3s agent
Used only for administration (VS Code, kubectl, helm, ansible, browser dashboards).
- Pi-hole (DNS)
- Nginx Proxy Manager (NPM) on Proxmox (reverse proxy to NodePorts)
strapi/— Strapi application source + Dockerfile.github/workflows/— CI pipelines (build/push + PR bump GitOps)gitops/— GitOps source of truthgitops/argocd/— ArgoCD applications (root + apps)gitops/apps/strapi/chart/— Strapi Helm chart +values.yaml(image.tag is updated via PR)gitops/helm/— Helm values for platform components (postgresql, kube-prometheus-stack)gitops/monitoring/— Kustomize app for Grafana dashboards (ConfigMaps)
platform/ansible/— Ansible inventory + playbooks for provisioning the 3 nodesplatform/helm/— values used to install ArgoCD (bootstrap)docs/— exam documentation:docs/HLD.mddocs/LLD.mddocs/VSM.mddocs/RUNBOOK.mddocs/DEMO_SCRIPT.md
Open these tabs:
- GitHub repo (PR list)
- GitHub Actions run
- Argo CD UI (applications page)
- Strapi admin URL
- Grafana dashboard
Suggested order:
- HLD: components + why GitOps (1–2 min)
- LLD: repo layout + where desired state lives (1–2 min)
- CI: show action builds/pushes image + opens PR bump (2–3 min)
- Money shot:
- open PR “bump Strapi image tag to
<sha>” - confirm it changes only
gitops/apps/strapi/chart/values.yaml - merge PR
- open PR “bump Strapi image tag to
- ArgoCD auto-sync + rollout (1–2 min)
- kubectl proof: pod image tag is new SHA (1 min)
- Grafana proof: Strapi dashboard (1–2 min)
- Future improvements (30–60 sec)
- Add Strapi app-level metrics (
/metrics) + ServiceMonitor (real RPS/latency/error rate) - Add SAST/secret scanning gates for the Strapi repo path
- Add image signing (cosign) + policy enforcement
- Add Vault for dynamic secrets (optional deep dive topic)
kubectl -n argocd get applications