Orientation
This course starts with someone who's never touched Kubernetes and ends at a production-shaped deployment of an Express.js app, running on self-managed Kubernetes on DigitalOcean droplets. Every chapter ends with a result you can verify.
"Production-shaped" is deliberate. The course teaches the practices, posture, and tooling a production team uses, but it doesn't produce a true production-grade system. The gaps are spelled out below (see Production Gaps), so what's missing is never a surprise.
What you will build
By the end of the course you'll have:
- A local workstation set up for Kubernetes development.
- A minimal Express.js application with
/,/healthz,/readyz, and/metricsendpoints. - Two self-managed Kubernetes clusters on DigitalOcean droplets (staging and production), each with one control plane and two workers, installed with
kubeadm, networked with Cilium, and integrated with DigitalOcean via the DO cloud-controller-manager and CSI driver. - An ingress edge with Traefik exposed through a real DigitalOcean Load Balancer (one per cluster, provisioned by
Service type=LoadBalancer),cert-manager, and real Let's Encrypt certificates on a real domain. - A GitHub Actions pipeline that builds container images with Kaniko (Chainguard fork), pushes them to the DigitalOcean Container Registry, and deploys to the cluster.
- PR status checks that gate merges on lint, tests, manifest validation, Trivy image scans, and the Docusaurus build.
- A tag-driven production deploy that requires protected
v*tags, atag-on-masterprovenance check, and a manual environment approval before applying to prod. - App manifests under Kustomize with
base/andoverlays/{staging,prod}. CRDs (ServiceMonitor,SealedSecret) land in the manifests only after their controllers are installed bycluster-bootstrap. - Sealed Secrets for sensitive configuration committed safely to git, with the controller's signing key backed up off-cluster so a destroyed cluster can still decrypt committed
SealedSecrets. metrics-serverinstalled in every cluster so the built-in CPU/memory HorizontalPodAutoscaler works.- A metrics stack with Prometheus, VictoriaMetrics for long-term storage, Grafana dashboards, and Alertmanager rules. Platform components use persistent volumes (see the platform-state exception below).
- Security hardening: scoped RBAC (CI is namespace-scoped and can't create Namespaces), default-deny NetworkPolicies, the
restrictedPod Security Standard, and Trivy-enforced image scanning. - Cluster health monitoring and a tested restore drill that recovers etcd and the sealed-secrets controller key.
- Production polish: resource requests and limits, PodDisruptionBudgets, graceful SIGTERM, and a rollback drill.
- A second deployment path using ArgoCD so you can compare push-based deploys with pull-based GitOps.
- The course site itself deployed as a static site via Appliku.
The stack at a glance
| Concern | Choice |
|---|---|
| Local cluster (early chapters) | kind |
| Production cluster | kubeadm on DigitalOcean droplets (no DOKS), kubelets running with --cloud-provider=external |
| Cluster networking (CNI) | Cilium |
| Cloud integration | DigitalOcean cloud-controller-manager (Load Balancers) + DO CSI driver (block storage for platform PVCs) |
| Metrics API for HPA | metrics-server |
| Environments | Two clusters: staging and prod |
| Ingress | Traefik exposed via Service type=LoadBalancer (real DO LB), with shared Middlewares for security headers; HTTP→HTTPS redirect handled at Traefik's EntryPoint |
| TLS | cert-manager with Let's Encrypt |
| Image registry | DigitalOcean Container Registry (DOCR) |
| Image builds in CI | Kaniko, Chainguard fork (the original GoogleContainerTools/kaniko repo was archived 2025-06-03) |
| Manifests | Kustomize (base + overlays per environment) |
| Secrets | Sealed Secrets (Bitnami); controller signing key backed up off-cluster |
| Metrics | Prometheus, VictoriaMetrics, Grafana, Alertmanager (all on PVCs — platform state exception) |
| Deploy model | GitHub Actions running kubectl apply -k (push) with protected tags + tag-on-master check, then ArgoCD (pull) |
| Course site hosting | Appliku |
Platform-state exception
The Express application is stateless. Platform components are not. Prometheus, VictoriaMetrics, Grafana, Alertmanager, and the sealed-secrets controller's keypair all need persistent storage. It's intentional, and it's the only place the course runs anything stateful. Application stateful workloads (databases, queues) stay out of scope.
Repositories used in the course
expressapp: the Express.js app, itsDockerfile, and its Kubernetes manifests underk8s/. The app'sbase/does not include aNamespaceobject; namespaces are created bycluster-bootstrapso the CI service account can stay namespace-scoped.cluster-bootstrap: the kubeadm install scripts and the cluster-level workloads. That's Cilium, DO cloud-controller-manager, DO CSI driver, metrics-server, Traefik (with sharedMiddlewares), cert-manager, sealed-secrets controller, kube-prometheus-stack, VictoriaMetrics, ArgoCD, and node-problem-detector. It also creates the per-environment app Namespaces and owns the backup CronJob/timer that captures etcd snapshots and the sealed-secrets key to DO Spaces.expressapp-manifests(created in part 13): split out fromexpressappto support the GitOps chapter.
Cost expectations
Once both clusters are running (Part 3 onward), the recurring DigitalOcean spend looks roughly like:
| Item | Approximate monthly cost |
|---|---|
| 3 droplets × 2 clusters (1 CP + 2 workers each) | $30–40 |
| 2 DigitalOcean Load Balancers (one per cluster's Traefik) | ~$24 |
| Block-storage volumes for platform PVCs (Prometheus, VictoriaMetrics, Grafana, Alertmanager × 2 clusters) | $5–15 |
| DOCR (starter tier) | ~$0–5 |
| DO Spaces (backups) | ~$5 |
| Total while both clusters are up | ~$70–90 / month |
Local chapters cost nothing. You can shut down the staging cluster between sessions to lower the bill.
How to use the course
Every chapter follows the same shape:
- What you will do: a one-paragraph summary of the chapter's goal.
- Prerequisites: what should already be in place before starting.
- Planned lessons: the steps inside the chapter.
- Completion signal: the concrete result that proves the chapter is done.
Don't skip the completion signal. Each one is the validation that the next chapter can build on what came before.
Prerequisites
- A GitHub account.
- A DigitalOcean account with billing enabled.
- A domain name you control (you can buy one in part 4 if you do not have one yet).
- Comfort with the Linux command line and basic Git.
Production Gaps
The course is production-shaped, not production-grade. The list below is the intentional gaps. If you ever run this in real production, close them in roughly this order:
- HA control plane. One control plane per cluster. Production needs three control planes with a load balancer or
kube-vipin front of the API. - Cluster upgrades.
kubeadm upgradeisn't covered. Real clusters need a rehearsed minor-version upgrade procedure. - Logs and traces. Metrics only. Production needs centralized logging (Loki, ELK) and distributed tracing (Tempo/Jaeger + OpenTelemetry).
- Application stateful workloads. No databases, queues, or caches run on the cluster.
- Sealed Secrets key rotation. Backup is required (covered); rotation isn't.
- Multi-region / disaster recovery. etcd snapshots cover cluster state, not regional failover.
- Image signing and SBOMs. Trivy catches CVEs; cosign/sigstore signing and SBOM publishing aren't covered.
- Audit logging. API server audit logs aren't configured.
- Cost and capacity monitoring. No VPA recommendations or cost tooling (OpenCost/Kubecost).
The full rationale for each gap lives in spec/initial.md.
Completion signal
You understand what the course will build, the stack it uses, the cost, and what's intentionally left out. You've got GitHub, DigitalOcean, and a domain ready to go.