A commit becomes a running, monitored deployment. On your laptop, in about five minutes.
Kestrel is a small European logistics company. Its parcel-tracking API,
kestrel-api, needs to ship several times a day without anyone running
kubectl apply against production, and it needs to be observable enough that
the on-call engineer finds out about a problem before customers do.
This repository is the delivery path that makes that true. GitHub Actions
builds and publishes the image, then writes the new tag back into the Helm
chart as a commit. Argo CD notices the commit and reconciles the cluster to
match. Prometheus scrapes the result, and Grafana and Alertmanager work off
SLO-based rules rather than arbitrary thresholds. The cluster is never pushed
to; it pulls. git revert is the rollback button.
flowchart LR
dev["Engineer"] -->|git push| repo[("GitHub<br/>gitops-k8s-platform")]
subgraph ci["GitHub Actions"]
direction TB
test["go test<br/>helm lint<br/>kubeconform"] --> img["buildx →<br/>GHCR"]
end
repo --> ci
img -.->|"writes image tag<br/>back as a commit"| repo
subgraph cluster["Kubernetes cluster (kind locally, EKS in production)"]
direction TB
argo["Argo CD<br/>reconcile loop"]
argo --> app["kestrel-api<br/>Deployment · HPA · PDB · NetworkPolicy"]
argo --> mon["kube-prometheus-stack"]
app -->|"/metrics"| prom["Prometheus"]
prom --> graf["Grafana<br/>RED dashboard"]
prom --> alert["Alertmanager<br/>error-budget alerts"]
end
repo -->|"Argo CD pulls,<br/>nothing pushes in"| argo
Needs docker, kind, kubectl and helm. No cloud account, no registry
login, nothing to pay for.
git clone https://github.com/thisiskazem/gitops-k8s-platform
cd gitops-k8s-platform
make demo # three-node kind cluster, Argo CD, Prometheus, the app
make traffic # 60s of mixed traffic so the dashboards have something to showmake demo ends with:
==> verifying the platform
pod placement across nodes:
kestrel-api-6cfd7c94b6-mkbx4 kestrel-worker
kestrel-api-6cfd7c94b6-vqp9z kestrel-worker2
calling the API from inside the cluster:
{"id":"KSL-4417","origin":"Turin, IT","destination":"Amsterdam, NL",
"status":"in_transit","updated_at":"2026-07-26T13:41:02Z"}
checking that Prometheus actually scrapes it:
ok kestrel-api is an active Prometheus target
Then open Grafana (make status prints the port-forward commands and the
password) and look at Kestrel / Kestrel API / RED. make down deletes
everything.
app/ Go service: RED metrics, split liveness/readiness, graceful drain
Dockerfile multi-stage → distroless, non-root, 18 MB, multi-arch
charts/kestrel-api/ Helm chart: Deployment, HPA, PDB, NetworkPolicy, ServiceMonitor
values-prod.yaml only the lines that genuinely differ in production
gitops/argocd/ AppProject + three Applications (app, monitoring, observability)
observability/ SLO alert rules and the Grafana dashboard, delivered by Argo CD
.github/workflows/ ci.yaml (test, lint, validate, publish) · release.yaml (promote)
hack/ kind cluster config and the demo script
| Skill | Where to look |
|---|---|
| Go | app/main.go, app/metrics.go: RED instrumentation, bounded label cardinality |
| Docker | app/Dockerfile: distroless, non-root, cross-compiled, cache mounts |
| Kubernetes | charts/kestrel-api/templates/: PDB, NetworkPolicy, topology spread, probe strategy |
| Helm | _helpers.tpl immutable selectors, .Capabilities CRD guard, minimal prod overlay |
| Argo CD | gitops/argocd/: AppProject as a blast-radius boundary, HPA drift handling, sync waves |
| GitHub Actions | ci.yaml least-privilege permissions, OIDC push to GHCR, no stored secrets |
| Prometheus | observability/alerts.yaml: multi-window multi-burn-rate SLO alerts |
| Grafana | observability/dashboard-kestrel-api.json: RED dashboard, version-controlled |
| Bash | hack/demo.sh: strict mode, idempotent, waits on conditions rather than sleeping |
Things that are simplified here, and why. A reviewer should be able to tell what is a decision and what is a shortcut.
make demoinstalls the app with Helm, not Argo CD. Argo CD pulls from a git remote, and a freshly cloned working tree has no published commit to pull from. The monitoring stack is genuinely reconciled by Argo CD, so the pull loop is real and visible. After pushing this repo,make gitopshands the app over togitops/argocd/app-kestrel-api.yamltoo. Same chart either way.- State lives in memory. A database would add a StatefulSet, migrations and backups without teaching anything more about delivery, which is what this repo is about.
- Control-plane scrape jobs are disabled. On kind these components bind to localhost; on EKS the control plane is managed by AWS. Leaving them enabled produces permanently-DOWN targets, and a dashboard that is always partly red is a dashboard nobody reads.
- Base images are pinned by tag, not digest. Digest pinning is correct for production but needs Renovate or Dependabot to stay current, and a repo full of stale digests is worse than one with readable tags.
- No CPU limit, only a memory limit. CPU is compressible, so a limit only buys throttling latency during a spike. Memory is not, so it gets a hard cap.
- The NetworkPolicy allows ingress from any namespace. Narrowing it needs a real client inventory; inventing one would be theatre.
The EKS cluster this deploys to in production is built by aws-landing-zone.
Kazem Bigdeli · Cloud, DevOps & Platform Engineer · Turin, Italy linkedin.com/in/kazembigdeli