Build a cluster, start to finish
The CKA doesn't hand you a multiple-choice question about a stuck PersistentVolumeClaim — it hands you a broken cluster and two hours to prove you can run one. Reading a Deployment spec doesn't build that reflex; running one, breaking it, and fixing it does. This hub is where you build it. You'll take taskboard — a small three-piece app (a stateless API, a static web frontend, and a Postgres database) — from an empty kind cluster through five connected parts to a cluster a real cluster administrator would trust: namespaced and access-controlled, running real workloads with real health checks, reachable through a real Ingress, backed by storage that survives a restart, and locked down with least-privilege RBAC. Each part opens by stating exactly what "done" looks like. If you only have an afternoon, the fast path below still gets you real reps across all five.
Imagine you're setting up a shared apartment building from scratch — not decorating one unit, the whole building. First you sort out the building itself: how many floors, who holds a key to the front door, which door each tenant's key opens. Then real tenants move in — the API family upstairs, the web family that greets visitors at the door, and the database family in the basement who keep every receipt anyone's handed them. You run mail slots and doorbells between units so the web family can actually ring the API's doorbell instead of shouting through the wall. You bolt the database family's filing cabinets to the floor so an argument two rooms over doesn't scatter last week's receipts. And you hand out keys that only open the doors each tenant needs — nobody gets a master key just because it's easier. Same one building, five jobs, in order.
Every part below is local and throwaway — nothing here touches a cloud account or costs real money. You'll want Docker (or Podman), kind, and kubectl; Part 3 adds the ingress-nginx controller and Part 5 goes easier with helm for a couple of add-ons — nothing here needs a paid tier or a cloud VM. Command flags and chart versions drift — always check each project's current docs against what's printed here. Tear everything down when you're done (kind delete cluster --name taskboard-dev) and nothing lingers on your machine.
The world you're building
☺ Like you're 10: Before any building starts, here's the map: one cluster, one small app with three pieces, two drawers to keep things tidy in, and one repo that grows a new room every part.
The hub you're reading and all five parts build inside one continuous cluster, so nothing you set up in Part 1 goes stale by Part 5. Here is the whole world, once, so you never have to re-derive it:
- The cluster is created with
kind create cluster --name taskboard-devand stays up for the whole capstone (minikube is a fine substitute; translate the commands yourself). - The app is taskboard — a small HTTP API with
GET /healthz,GET /boards/:id, andPOST /boards/:id/cards, a static web frontend that calls it, and a single-replica Postgres instance behind both. Images are built locally astaskboard-api:devandtaskboard-web:devand loaded straight into the cluster withkind load docker-image— no registry needed for a lab this size. - Namespaces split cleanly in two:
taskboardhosts the app itself, andplatformhosts every add-on — ingress-nginx, metrics-server, and anything you bolt on in going further. Every part keeps this split consistent. - The repo is a single git repo,
taskboard, that starts with justapi/,web/, and two Dockerfiles, and grows exactly one newmanifests/subdirectory per part.
taskboard-dev (kind cluster)
├── namespace: platform # ingress-nginx, metrics-server, add-ons from "going further"…
├── namespace: taskboard # api + web Deployments, postgres StatefulSet, Services, NetworkPolicies, RBAC…
└── (git) taskboard/
├── api/, web/, Dockerfile.api, Dockerfile.web # the bare repo you start with
├── manifests/01-cluster/ # Part 1 — cluster foundation
├── manifests/02-workloads/ # Part 2 — workloads & config
├── manifests/03-networking/ # Part 3 — networking & ingress
├── manifests/04-storage/ # Part 4 — storage & stateful apps
└── manifests/05-security/ # Part 5 — security & RBACThe cluster itself is created once, from a config that reserves host ports 80 and 443 so Part 3's Ingress has somewhere real to answer from:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: taskboard-dev
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
- containerPort: 443
hostPort: 443Every part opens by stating exactly what state your cluster and repo are in when you arrive (inherited from the part before) and what state they're in when you leave (handed to the next one). If a part ever feels like it's starting from nothing, re-read its opening paragraph — you're missing something the previous part was supposed to leave behind.
The five parts
☺ Like you're 10: Five chapters of the same story. Each one hands the next chapter a cluster that's a little more finished than the one before.
Work them in order. Each part pairs with a slice of the official CKA curriculum, so you always know which domain your hands are earning you reps in. Troubleshooting (Domain 5, 30% — the single largest domain on the exam) never gets a part of its own, because on the real exam it never announces itself as one task either; instead, every part below ends by breaking something on purpose that you have to diagnose with nothing but kubectl describe, kubectl logs, and kubectl get events — same as Foxy would.
| # | Part | Pairs with | Done when |
|---|---|---|---|
| 1 | Cluster Foundation — 🦉 Owl & 🦫 Benny | Cluster Architecture, Installation & Configuration (D1, 25%) · kind · kubeadm | kubectl get nodes shows a Ready node, both namespaces and a baseline RBAC ServiceAccount exist, and a deliberately broken kubeconfig context gets diagnosed and fixed. |
| 2 | Workloads & Config — 🦫 Benny & 🐿️ Nutty | Workloads & Scheduling (D2, 15%) | a rolling update completes with zero dropped requests, and a Deployment with a broken readiness probe never reaches Ready — caught before it takes traffic. |
| 3 | Networking & Ingress — 🐦 Pip | Services & Networking (D3, 20%) · Networking & the CNI | web reaches api by its Service DNS name, an external curl through Ingress reaches both paths, and a default-deny NetworkPolicy blocks exactly the traffic it should. |
| 4 | Storage & Stateful Apps — 🐘 Ellie | Storage (D4, 10%) · Storage & the CSI | a row written before postgres's Pod is deleted is still there after it reschedules, and a PVC requested against a StorageClass that doesn't exist gets diagnosed straight from kubectl describe pvc. |
| 5 | Security & RBAC — 🐢 Timmy | RBAC & Admission Control · Security: Defense in Depth (RBAC slice of D1) | kubectl auth can-i confirms api's ServiceAccount can do exactly what it needs and nothing else, and a manifest requesting root or :latest is refused before it ever schedules. |
Deep-dive the concept behind any part via its linked lesson before or after you build — the lesson explains why, the part makes you prove it works. For the mechanics this app is standing on, Platform Engineering's Kubernetes-as-Substrate covers the same object model one altitude up, and for a genuinely adversarial pass at Part 5's RBAC once you're comfortable, DevSecOps' Kubernetes Security Deep Dive walks the same objects from an attacker's-eye view.
The fast path — for an afternoon
☺ Like you're 10: No time for five chapters? Here's the one-page summary of the whole story, still worth doing.
The five parts are the deep version of this track — real manifests, real breakage, real triage. If you only have an afternoon, or want one condensed pass before the exam, work this shorter nine-item version instead. It touches every domain at a lighter depth, using the exact same taskboard/taskboard-dev world as the parts above, so nothing you build here is wasted if you come back and go deep later.
kind, and kubectl. Create the cluster from the config above with kind create cluster --name taskboard-dev --config kind-config.yaml, which reserves host ports 80/443 for Part 3's Ingress and labels the node ingress-ready=true.kubectl cluster-info shows the control plane reachable, and kubectl get nodes shows one Ready node carrying the ingress-ready label.taskboard and platform namespaces. Create a ServiceAccount taskboard-api in taskboard with a Role scoped to reading exactly one named ConfigMap — nothing wider. Verify with kubectl auth can-i, impersonating that ServiceAccount via --as, before you ever run a Pod under it.kubectl auth can-i get configmaps -n taskboard --as=system:serviceaccount:taskboard:taskboard-api answers yes, and the same command with delete instead of get answers no.taskboard-api:dev and taskboard-web:dev locally, load them straight into the cluster with kind load docker-image taskboard-api:dev --name taskboard-dev, then deploy both with readiness and liveness probes on /healthz and real resources.requests/limits. Roll out a typo'd image tag on purpose and watch the rollout stall instead of replacing healthy Pods.kubectl rollout status deployment/api completes clean on a good image, and the bad-tag rollout visibly stalls in rollout status without ever reaching Ready.envFrom — no value baked into the image. Label every object consistently (app: taskboard, component: api|web|postgres) and select on those labels everywhere instead of by name.kubectl get pods -n taskboard -l app=taskboard,component=api returns exactly the api Pods, and editing the ConfigMap plus a rollout restart changes behavior with zero image rebuild.web and api. Install ingress-nginx into platform and add an Ingress routing / to web and /api to api. Confirm web reaches api through its Service DNS name, never a hardcoded Pod IP.curl localhost/ and curl localhost/api/healthz both succeed through the Ingress, and web's own logs show a call to http://api.taskboard.svc.cluster.local, not an IP address.taskboard, then add narrow allow rules: web can reach api on 8080, api can reach postgres on 5432 — nothing else gets in. Run a plain busybox Pod outside those rules and try to curl api directly from it.curl to api times out, while web's own traffic to api keeps working exactly as before.kubectl describe pvc on the broken claim names the missing StorageClass as the reason it's stuck Pending.kubectl describe, kubectl logs --previous, and kubectl get events, no guessing. Then right-size api's requests/limits from a real kubectl top pod reading instead of a round guess.requests/limits come from an actual kubectl top pod number.kubectl auth can-i --list --as=system:serviceaccount:taskboard:taskboard-api and confirm it's a short list, not cluster-admin. Count your own objects: two namespaces, two Deployments, a StatefulSet, three Services, an Ingress, three NetworkPolicies, a Role and a RoleBinding. Then delete the whole kind cluster and rebuild it from your manifests/ alone, with no memory required, and time how long a from-scratch rebuild actually takes.kubectl edit, using only the manifests already in your repo — and you have a real number for how long that took."Notice something: I don't get my own item on that list. That's not an oversight — Troubleshooting is 30% of this exam, the biggest single domain there is, and it never shows up as one clean task on the real thing either. It shows up as 'why is this broken,' hiding inside whatever you were already building. If you did all nine and never once had to run kubectl describe on something that wasn't working, go back and actually break something. The exam will."
What you'll have built
☺ Like you're 10: By the end, your laptop holds a small working version of the whole building this course keeps describing — the floors, the tenants, the mail slots, the bolted-down cabinets, and the keys that only open what they should.
Finish the five parts (or the fast path) and you'll have hands-on reps across every domain of the CKA curriculum, weighted the way the real exam weights it: Cluster Architecture, Installation & Configuration (D1, 25%), Workloads & Scheduling (D2, 15%), Services & Networking (D3, 20%), Storage (D4, 10%), and Troubleshooting (D5, 30%) — the full 100%, since the last one is threaded through every part rather than boxed into one. That matters more here than it would for a multiple-choice exam: the CKA is graded on the end state of a live cluster, not on a bubble sheet, so the only real practice is a cluster you built, broke, and fixed with your own hands. Pair this with this course's Certifications hub and CKA Study Plan once your reps are in, and if the Kubestronaut ladder is the eventual goal, the other nine CNCF certifications plus the LFCS live in this platform's Golden Astronaut course once CKA, CKAD, and CKS are behind you.
The CKA is performance-based — a live terminal against real clusters, not multiple choice — graded on the end state of the cluster, not your working. Duration, price, pass mark, and the exact curriculum weights quoted above all change over time, and this is an independent, unofficial study resource, not affiliated with the CNCF or the Linux Foundation. Confirm the current numbers on the Linux Foundation's own CKA page and the curriculum published at github.com/cncf/curriculum before you register.
Already comfortable? Level up any part: put manifests/ under real GitOps so the cluster reconciles from your repo instead of you running kubectl apply by hand; add a HorizontalPodAutoscaler to api and load-test it into actually scaling; swap the single-node kind cluster for a three-node one and force a real reschedule with kubectl drain; put a service mesh sidecar in front of api and compare its mTLS to your Part 5 NetworkPolicy; or wire real TLS through Ingress with cert-manager instead of plain HTTP.
Foxy: Five parts for one little task app? Can't I just skim the fast path and call it done?
Benny the Beaver: You can, Foxy, and you'll still walk away with real reps. But the fast path is the map — the five parts are the actual territory, with a PVC that really gets stuck and a rollout that really stalls.
Timmy the Turtle: And I'm not signing off Part 5 until kubectl auth can-i --list for api's ServiceAccount comes back short. A long list there is a long list of ways to lose the whole cluster to one bug in one service.
Gizmo the Gremlin: Or... skip the Role entirely, bind cluster-admin straight to the ServiceAccount, ship it Friday, nobody's ever denied anything. 🤑
Timmy the Turtle: That's not "nobody's denied" — that's "one bug in api is now a bug with root on the entire cluster." Absolutely not.
Ellie the Elephant: Whichever part you're on, I'm logging it either way — what broke, what fixed it, how long it took. Ask me in a month and I'll still have the record.
Professor Owl: One cluster, five parts, one loop. Go build it.
1. Which part proves your storage is actually durable, and what two-part test proves it? 2. Which part stops a compromised api Pod from becoming a compromise of the whole cluster? 3. Why doesn't Troubleshooting get a part of its own, even though it's worth more of the exam (30%) than any other single domain? 4. What's the one command that tells you whether a RoleBinding actually grants what you think it grants, without handing out a real credential to find out?
Check your answers
- Part 4 — Storage & Stateful Apps. The two-part test: a row written before you delete the postgres Pod is still there after it reschedules, and a PVC requested against a StorageClass that doesn't exist gets caught by
kubectl describe pvcinstead of hanging silently. - Part 5 — Security & RBAC. Its least-privilege Role means a compromised api ServiceAccount can only do the handful of things api itself actually needs — not everything a
cluster-adminbinding would allow. - Because on the real exam it doesn't announce itself as one clean task either — it hides inside whatever you were already building. Every part above ends by breaking something on purpose so the reflex gets built five separate times, not memorized once.
kubectl auth can-i, which can impersonate any subject with--as(and--as-group) before you ever grant that subject a real credential.