Where to go next
Your path beyond the basics.
You can run an app as a Deployment (keep N copies running), replace a dead copy, roll out and roll back, feed it ConfigMaps and Secrets, put a Service and Ingress in front, attach storage that survives a restart, use namespaces, and set resource limits. That is a real foundation for running workloads on Kubernetes.
Nothing sticks until you build something
Reading is not a substitute for a cluster you can break. Three free tools run Kubernetes on a laptop: minikube, kind (Kubernetes IN Docker), and k3s (a lightweight single-file build). Pick one, start it, and check that its node comes up Ready. kubectl is how you talk to it.
$ minikube start😄 minikube v1.34.0 on your laptop✨ Automatically selected the docker driver🐳 Preparing Kubernetes v1.31.0 on Docker 27.2.0 ...🌟 Enabled addons: default-storageclass, storage-provisioner🏄 Done! kubectl is now configured to use "minikube".$ kubectl get nodesNAME STATUS ROLES AGE VERSIONminikube Ready control-plane 40s v1.31.0
Then take one small app all the way around the loop, adding a piece at a time: deploy it, expose it, configure it, give it storage, scale it up, ship a new version, then roll that version back. Each piece is a manifest, a text file that spells out what you want, written in a plain format called YAML. Here is a complete one you can apply exactly as it stands. It comes in two parts joined by the '---' line: a Deployment that keeps two copies of a web server running, and a Service that puts one stable address in front of both.
apiVersion: apps/v1kind: Deploymentmetadata:name: hellospec:replicas: 2selector:matchLabels:app: hellotemplate:metadata:labels:app: hellospec:containers:- name: webimage: nginx:1.27ports:- containerPort: 80readinessProbe:httpGet:path: /port: 80initialDelaySeconds: 2periodSeconds: 5---apiVersion: v1kind: Servicemetadata:name: hellospec:selector:app: helloports:- port: 80targetPort: 80
$ kubectl apply -f deploy.yamldeployment.apps/hello createdservice/hello created$ kubectl get deploy,pods,svcNAME READY UP-TO-DATE AVAILABLE AGEdeployment.apps/hello 2/2 2 2 18sNAME READY STATUS RESTARTS AGEpod/hello-7c9f8b6d4b-4xk2p 1/1 Running 0 18spod/hello-7c9f8b6d4b-lm7qd 1/1 Running 0 18sNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEservice/hello ClusterIP 10.96.140.22 <none> 80/TCP 18s
Read that output slowly, because it is the whole point of the course. The Deployment line says READY 2/2, so both copies you asked for are up. Under it sit two lines starting with pod/, and each of those is one running copy of the web server. A copy only counts as READY once it passes the readinessProbe from the file, which is Kubernetes knocking on the app's front door with a small web request to the path / and waiting for an answer before it sends any real users that way. The Service shows a CLUSTER-IP of 10.96.140.22 and an EXTERNAL-IP of none, which is Kubernetes telling you that address works from inside the cluster and nowhere else. To open the app in your own browser, have your laptop hand traffic from a local port straight to the Service with kubectl port-forward service/hello 8080:80, then visit http://localhost:8080.
Put that file in Git on day one. Git is version control: it records each change with a time and a message, so you can see what changed and undo it. One-off kubectl commands are fast and gone from memory an hour later. Files you apply can be reviewed and replayed. That is how teams run Kubernetes. Watch the second apply: 'unchanged' means the cluster already matches the file.
$ git init && git add deploy.yaml && git commit -m "hello app: deployment + service"Initialized empty Git repository in /home/you/hello/.git/[main (root-commit) 9f1c2a0] hello app: deployment + service1 file changed, 36 insertions(+)$ kubectl apply -f .deployment.apps/hello unchangedservice/hello unchanged
Break it on purpose, before it breaks on you
The single most useful thing you can practise is recovering from a bad deploy, because a bad deploy is coming for you eventually. So cause one right now, while nothing is at stake. Open deploy.yaml, change the image line to nginx:1.72 (there is no such version), and apply it. Then watch what Kubernetes does with a version it cannot get hold of.
$ kubectl apply -f deploy.yamldeployment.apps/hello configuredservice/hello unchanged$ kubectl get podsNAME READY STATUS RESTARTS AGEhello-7c9f8b6d4b-4xk2p 1/1 Running 0 6mhello-7c9f8b6d4b-lm7qd 1/1 Running 0 6mhello-5d8c4f7b9c-p2wqr 0/1 ImagePullBackOff 0 25s
Read that carefully, because most of your real problems will arrive in this exact shape. Your two original copies are still Running and still serving users. The new copy is stuck at ImagePullBackOff: Kubernetes tried to download the image, failed, and is now waiting a little longer before each retry. It refuses to retire a working copy until a new one is healthy, so nobody outside notices a thing. To find out why the new copy is unhappy, ask the copy itself with kubectl describe pod.
$ kubectl describe pod hello-5d8c4f7b9c-p2wqr...Events:Type Reason Age From Message---- ------ ---- ---- -------Normal Scheduled 46s default-scheduler Successfully assigned default/hello-5d8c4f7b9c-p2wqr to minikubeNormal Pulling 45s kubelet Pulling image "nginx:1.72"Warning Failed 44s kubelet Failed to pull image "nginx:1.72": manifest for nginx:1.72 not found: manifest unknownWarning Failed 44s kubelet Error: ErrImagePullNormal BackOff 18s (x2 over 43s) kubelet Back-off pulling image "nginx:1.72"
That Events list is the control plane thinking out loud. default-scheduler picked a node for the new copy. Then kubelet, the agent running on that node, tried to pull the image and reported back 'manifest ... not found'. There is no nginx:1.72. So now you know what you are dealing with: a typo in the tag, not a broken app and not a broken cluster. The fix is one command. Undo the rollout and drop back to the version that worked.
$ kubectl rollout undo deployment/hellodeployment.apps/hello rolled back$ kubectl get podsNAME READY STATUS RESTARTS AGEhello-7c9f8b6d4b-4xk2p 1/1 Running 0 8mhello-7c9f8b6d4b-lm7qd 1/1 Running 0 8m
Nothing went down. The Deployment kept the two good copies serving and never sent a single user to the broken one. That same typo in a plain docker run on one box would have taken the site with it. rollout undo has you running again in seconds, and then you fix the tag back in deploy.yaml so the file and the running cluster tell the same story again.
Three roads out of here
Once that loop feels boring, pick a direction. Three come up most. If you want to run the cluster itself, administration covers the control plane (where placement and failure handling are decided), networking and storage internals, upgrades, and troubleshooting. If you want to defend clusters, security covers RBAC (Role-Based Access Control: who may do what), network policy, and later CKS (Certified Kubernetes Security Specialist). If you want smoother day-to-day work, the ecosystem is Argo CD (GitOps: apply Git to the cluster), Helm and Kustomize for templating manifests, and Prometheus with Grafana for metrics. Do not walk all three at once. Pick the one that matches what you want next. None of it is required to be useful today.
Here is what that foundation actually buys you on a team. Every course after this one, administration, packaging, security, opens by assuming you can already run the loop you ran a few minutes ago: apply a manifest, read the output, work out which piece is unhappy, and put it back. Nobody teaches that part twice. Build the reflex now, while a broken cluster costs you nothing.
If you want one concrete assignment, take this one. Build a small personal project on kind or minikube with five pieces in it: a Deployment, a ConfigMap, a Service, an Ingress, and a PVC (PersistentVolumeClaim, your app's written request for a slice of disk that outlives the Pod). Then break it deliberately and repair it using describe and logs. An hour of that beats rereading the analogies in this course, mine included.
The unglamorous stuff is what shows up during a real incident. A Service with no endpoints behind it. A readiness probe aimed at a path the app never serves. kubectl pointed at the wrong cluster. A PersistentVolumeClaim sitting in Pending because nothing in the cluster can satisfy it. Keep those kubectl reflexes sharp. They will carry you much further than sprinting ahead to exotic tooling.
Try this
Take stock of what you can already reach for without thinking. List every Deployment, Service and Ingress across the whole cluster, check which kinds of object live inside a namespace, and confirm which cluster kubectl is currently pointed at. Then keep a scratch file of the commands you typed most in this course. That file becomes your own cheat sheet, and it will serve you better than anyone else's.
$ kubectl get deploy,svc,ingress -A | Select-Object -First 30NAMESPACE NAME READYdefault deployment.apps/… 1/1...NAMESPACE NAME TYPE CLUSTER-IPdefault service/kubernetes ClusterIP 10.96.0.1...$ kubectl api-resources --namespaced=true | Select-String 'deployments|services|configmaps|secrets|ingresses|persistentvolumeclaims'configmaps cm true ConfigMapdeployments deploy true Deploymentingresses ing true Ingresspersistentvolumeclaims pvc true PersistentVolumeClaimsecrets true Secretservices svc true Service$ kubectl config current-contextkind-kind
Takeaway
The fundamentals are Deployments, Services, configuration, storage and namespaces, practised with kubectl until they bore you. Your next move is the hello app from this lesson, taken from first apply to broken tag to rollout undo without looking anything up. Specialise into administration, packaging or security after that.