Learn kubectl from zero to production-ready in 10 focused days.
Each day has a clear goal, key concepts, commands to practice, and a checkpoint task to commit to your repo.
- Prerequisites
- How to Use This Guide
- Roadmap Overview
- Day 1 – Why kubectl Exists
- Day 2 – First kubectl Commands
- Day 3 – Pods (The Core Unit)
- Day 4 – Deployments & ReplicaSets
- Day 5 – Services & Networking
- Day 6 – Config & Secrets
- Day 7 – Debugging Like a Pro
- Day 8 – YAML Mastery
- Day 9 – Multiple Environments
- Day 10 – Mini Project
- Progress Tracker
- Useful Resources
You don’t need Kubernetes experience. You just need:
- A terminal (Linux/macOS/WSL on Windows)
- Basic command-line comfort (
cd,ls,cat) - A cluster to practice on — pick one:
- Minikube — runs locally on your machine
- Kind — Kubernetes in Docker
- k3s — lightweight, great for VMs
- Play with Kubernetes — free browser-based cluster
- Fork or clone this repo to track your progress
- Work through one day at a time — don’t skip ahead
- Run every command yourself — reading is not enough
- Complete the checkpoint at the end of each day
- Commit your work — push your YAML files and notes to this repo as you go
kubectl-learning/
├── README.md
├── day-01/
│ └── notes.md
├── day-02/
│ └── notes.md
├── day-03/
│ ├── my-first-pod.yaml
│ └── notes.md
├── day-04/
│ ├── deployment.yaml
│ └── notes.md
...
└── day-10/
├── deployment.yaml
├── service.yaml
├── configmap.yaml
├── secret.yaml
└── notes.md
| Day | Topic | Goal |
|---|---|---|
| 1 | Why kubectl exists | Understand the problem it solves |
| 2 | First commands | Get comfortable with the CLI |
| 3 | Pods | Understand what actually runs |
| 4 | Deployments & ReplicaSets | Run apps the Kubernetes way |
| 5 | Services & Networking | Make apps reachable |
| 6 | Config & Secrets | Stop hardcoding configs |
| 7 | Debugging | Be useful during outages |
| 8 | YAML mastery | Read & write manifests confidently |
| 9 | Multiple environments | Real-world kubectl usage |
| 10 | Mini project | Cement everything |
Goal: Understand the problem kubectl solves before touching any commands.
- Kubernetes is a system that manages containers across multiple machines. It has two parts:
- Control plane — the brain (API server, scheduler, etcd)
- Nodes — the workers that actually run your containers
- kubectl is a command-line tool that talks to the Kubernetes API server. It’s how you tell Kubernetes what you want.
- Declarative vs Imperative:
- Imperative — “Do this now” (
kubectl run,kubectl delete) - Declarative — “Here’s what I want, make it so” (
kubectl apply -f file.yaml) - Kubernetes prefers declarative. So should you.
- Imperative — “Do this now” (
- kubectl vs other tools:
- Kubernetes Dashboard — GUI, good for viewing, not for automation
- Helm — package manager built on top of kubectl
- GitOps tools (ArgoCD, Flux) — apply YAML from Git automatically; kubectl is still under the hood
Install kubectl:
# macOS
brew install kubectl
# Linux
curl -LO "https://dl.k8s.io/release/$(curl -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl && sudo mv kubectl /usr/local/bin/
# Verify
kubectl version --clientStart a local cluster (choose one):
# Minikube
minikube start
# Kind
kind create cluster
# Verify cluster access
kubectl cluster-info
kubectl get nodes- kubectl installed and
kubectl version --clientshows output - Cluster running and
kubectl get nodesshows a Ready node - Create
day-01/notes.mdexplaining in your own words: what is the API server, and why does kubectl talk to it?
Goal: Get comfortable navigating a cluster from the CLI.
# List resources
kubectl get pods
kubectl get nodes
kubectl get all
# Get more details
kubectl get pods -o wide # shows IP, node
kubectl get pods -o yaml # full YAML output
# Inspect a specific resource
kubectl describe pod <pod-name> # human-readable details + events
# Understand any resource
kubectl explain pod
kubectl explain pod.spec
kubectl explain deployment.spec.templateNamespaces are like folders — they group and isolate resources.
kubectl get namespaces
kubectl get pods --namespace kube-system # system pods
kubectl get pods -n kube-system # shorthand
kubectl get pods --all-namespaces # everythingA context = cluster + user + namespace. Useful when you have multiple clusters.
kubectl config view # see all contexts
kubectl config current-context # which context is active
kubectl config use-context <name> # switch context
kubectl config get-contexts # list all contexts- Run
kubectl get all -n kube-systemand understand what you see - Use
kubectl explainon at least 3 different resources - Create
day-02/notes.md: What’s the difference betweengetanddescribe?
Goal: Understand what actually runs in Kubernetes.
- The smallest deployable unit in Kubernetes
- Wraps one or more containers that share network and storage
- Pods are ephemeral — they die, they don’t come back on their own
- You rarely create pods directly in production (use Deployments instead)
# Run a pod quickly
kubectl run my-pod --image=nginx
# Check it
kubectl get pods
kubectl describe pod my-podSave this as day-03/my-first-pod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
labels:
app: demo
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80# Apply it
kubectl apply -f day-03/my-first-pod.yaml
# Difference between apply and create:
# apply — creates if not exists, updates if exists (idempotent)
# create — fails if resource already exists| Phase | Meaning |
|---|---|
| Pending | Scheduled but container not started yet |
| Running | At least one container is running |
| Succeeded | All containers exited successfully |
| Failed | At least one container exited with error |
| Unknown | Node communication lost |
# View logs
kubectl logs my-pod
kubectl logs my-pod -f # follow (stream live)
kubectl logs my-pod --previous # logs from last crashed container
# Execute commands inside a container
kubectl exec -it my-pod -- /bin/bash
kubectl exec -it my-pod -- ls /etc/nginx
kubectl exec my-pod -- cat /etc/hosts
# Clean up
kubectl delete pod my-pod
kubectl delete -f day-03/my-first-pod.yaml- Create a pod using YAML and commit the file to
day-03/ - Shell into the pod and run a command
- Create
day-03/notes.md: Why are pods ephemeral, and why does that matter?
Goal: Run apps the right way in Kubernetes.
| Problem | What Happens |
|---|---|
| Pod crashes | It’s gone — nothing restarts it |
| Need 3 copies | You have to create 3 pods manually |
| Want to update your app | You have to delete and recreate pods |
Deployments solve all of this.
Deployment
└── manages ReplicaSet
└── manages Pods (n replicas)
A ReplicaSet ensures N copies of a pod are always running. A Deployment manages ReplicaSets and handles updates.
# Imperative
kubectl create deployment my-app --image=nginx --replicas=3
# Check what was created
kubectl get deployments
kubectl get replicasets
kubectl get podsSave as day-04/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80kubectl apply -f day-04/deployment.yamlkubectl scale deployment my-app --replicas=5
kubectl get pods # watch 5 pods come up# Update the image (triggers a rolling update)
kubectl set image deployment/my-app nginx=nginx:1.26
# Watch the rollout
kubectl rollout status deployment/my-app
# See history
kubectl rollout history deployment/my-app
# Undo last update
kubectl rollout undo deployment/my-app
# Undo to a specific revision
kubectl rollout undo deployment/my-app --to-revision=1- Deploy 3 replicas, then scale to 5
- Do a rolling update and watch
kubectl rollout status - Roll back and confirm the old image is running
- Commit
day-04/deployment.yaml
Goal: Make your application reachable.
Pods have IP addresses, but they change when pods restart. You need a stable endpoint to reach your app. That’s what a Service is.
| Type | Access | When to Use |
|---|---|---|
ClusterIP |
Inside cluster only (default) | Internal microservices |
NodePort |
Via node’s IP + a port (30000-32767) | Local dev / testing |
LoadBalancer |
External IP via cloud load balancer | Production on cloud |
Services use label selectors to find pods. If a pod has the label app: my-app, a service with selector app: my-app will route traffic to it.
# Imperative
kubectl expose deployment my-app --port=80 --type=NodePort
# Check it
kubectl get svc
kubectl describe svc my-appSave as day-05/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
spec:
selector:
app: my-app # matches pods with this label
ports:
- port: 80 # service port
targetPort: 80 # container port
type: NodePortkubectl apply -f day-05/service.yaml
# On Minikube, get the URL
minikube service my-app-svc --url
# Port-forward (works on any cluster)
kubectl port-forward svc/my-app-svc 8080:80
# Now open http://localhost:8080- Expose your deployment and reach it from your browser or
curl - Commit
day-05/service.yaml - Create
day-05/notes.md: What would happen if a pod’s label didn’t match the service selector?
Goal: Never hardcode configuration again.
Store non-sensitive config (URLs, feature flags, settings).
# Create from literal values
kubectl create configmap app-config \
--from-literal=APP_ENV=production \
--from-literal=LOG_LEVEL=info
# Create from a file
kubectl create configmap app-config --from-file=config.properties
# View it
kubectl get configmap app-config -o yamlSave as day-06/configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_ENV: "production"
LOG_LEVEL: "info"
DATABASE_URL: "postgres://db-service:5432/mydb"Store sensitive data (passwords, tokens, API keys). Base64-encoded, not encrypted by default — but can be encrypted at rest.
# Create a secret
kubectl create secret generic db-secret \
--from-literal=DB_PASSWORD=supersecret \
--from-literal=API_KEY=myapikey123
# View it (values are base64 encoded)
kubectl get secret db-secret -o yaml
# Decode a value
kubectl get secret db-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -dapiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: nginx
env:
# From ConfigMap
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: app-config
key: APP_ENV
# From Secret
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: DB_PASSWORD- Create a ConfigMap and a Secret
- Deploy an app that reads from both
- Exec into the pod and verify:
kubectl exec -it <pod> -- env | grep APP_ENV - Commit
day-06/configmap.yaml(never commit real secrets to Git!)
Goal: Be useful when things break.
1. kubectl get pods → Is the pod running?
2. kubectl describe pod → What events happened?
3. kubectl logs → What did the app say?
4. kubectl exec → Can I get inside?
CrashLoopBackOff
kubectl describe pod <pod-name> # look at Events section
kubectl logs <pod-name> # app crash logs
kubectl logs <pod-name> --previous # logs from before last crashCauses: app error on startup, bad config, missing env vars, wrong command
ImagePullBackOff / ErrImagePull
kubectl describe pod <pod-name> # check Events for image pull errorCauses: wrong image name, wrong tag, private registry without credentials
Pending Pod (never starts)
kubectl describe pod <pod-name> # look for "Insufficient CPU/memory"
kubectl get nodes # are nodes Ready?Causes: not enough resources, node selector mismatch, taints
OOMKilled (Out of Memory)
kubectl describe pod <pod-name> # look for OOMKilled in Last StateCauses: memory limit too low, memory leak in app
# Watch pods in real time
kubectl get pods -w
# Get events for the whole namespace
kubectl get events --sort-by=.lastTimestamp
# Describe a node (check capacity)
kubectl describe node <node-name>
# Run a temporary debug pod
kubectl run debug --image=busybox --rm -it --restart=Never -- sh
# Check resource usage (requires metrics-server)
kubectl top pods
kubectl top nodes- Deliberately break a deployment (wrong image tag, e.g.
nginx:doesnotexist) - Debug it using only
describe,logs, andevents - Fix it with a correct image and do a rollout
- Create
day-07/notes.md: Walk through your debug session step by step
Goal: Read and write Kubernetes manifests confidently.
apiVersion: apps/v1 # API group + version
kind: Deployment # what type of resource
metadata: # name, namespace, labels, annotations
name: my-app
namespace: default
labels:
app: my-app
env: production
annotations:
description: "My production app"
spec: # desired state — differs per resource type
...| Labels | Annotations | |
|---|---|---|
| Purpose | Selection & grouping | Metadata & documentation |
| Used by | Selectors, services | Tools, humans |
| Queryable | Yes (-l app=my-app) |
No |
spec:
containers:
- name: app
image: nginx
resources:
requests: # minimum guaranteed
cpu: "100m" # 100 millicores = 0.1 CPU
memory: "128Mi"
limits: # maximum allowed
cpu: "500m"
memory: "256Mi"spec:
containers:
- name: app
image: nginx
livenessProbe: # if this fails → restart container
httpGet:
path: /healthz
port: 80
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe: # if this fails → remove from Service endpoints
httpGet:
path: /ready
port: 80
initialDelaySeconds: 5
periodSeconds: 3# Dry run — check without applying
kubectl apply -f my-file.yaml --dry-run=client
# Validate server-side
kubectl apply -f my-file.yaml --dry-run=server
# Diff what would change
kubectl diff -f my-file.yaml- Write a complete Deployment YAML from scratch (no copy-paste)
- Include: resource limits, a liveness probe, labels, annotations
- Validate with
--dry-run=clientbefore applying - Commit as
day-08/deployment-with-probes.yaml
Goal: Manage apps across dev, staging, and production.
# Create namespaces
kubectl create namespace dev
kubectl create namespace staging
kubectl create namespace production
# Deploy to a specific namespace
kubectl apply -f deployment.yaml -n dev
kubectl apply -f deployment.yaml -n staging
# View resources per namespace
kubectl get all -n dev
kubectl get all -n stagingA kubeconfig file (usually at ~/.kube/config) holds all your cluster connections.
# View all contexts
kubectl config get-contexts
# Switch context
kubectl config use-context my-prod-cluster
# Set a default namespace for a context
kubectl config set-context --current --namespace=dev
# Rename a context
kubectl config rename-context old-name new-nameTip: Install kubectx and kubens for faster switching:
brew install kubectx # includes both kubectx and kubens
kubectx # list contexts
kubectx my-prod # switch cluster
kubens dev # switch namespace# Always check which context you're in before destructive operations
kubectl config current-context
# Use --dry-run before applying in production
kubectl apply -f deployment.yaml --dry-run=server -n production
# Use aliases to reduce mistakes
alias kdev='kubectl -n dev'
alias kprod='kubectl -n production'- Create
devandstagingnamespaces - Deploy your app to both with different ConfigMap values
- Practice switching between them with
kubectx/kubensorkubectl config use-context - Create
day-09/notes.md: How would you prevent someone from accidentally runningkubectl deletein production?
Goal: Tie everything together into one real-world workflow.
A complete application stack managed entirely with kubectl:
nginx (web server)
├── Deployment (3 replicas)
├── Service (NodePort)
├── ConfigMap (app settings)
└── Secret (fake API key)
kubectl create namespace mini-projectSave each file in day-10/, then:
kubectl apply -f day-10/ -n mini-projectday-10/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_NAME: "My kubectl App"
LOG_LEVEL: "info"day-10/secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: app-secret
type: Opaque
data:
API_KEY: bXlzZWNyZXRrZXkxMjM= # base64 of "mysecretkey123"day-10/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
env:
- name: APP_NAME
valueFrom:
configMapKeyRef:
name: app-config
key: APP_NAME
- name: API_KEY
valueFrom:
secretKeyRef:
name: app-secret
key: API_KEY
resources:
requests:
cpu: "100m"
memory: "64Mi"
limits:
cpu: "200m"
memory: "128Mi"
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5day-10/service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 80
type: NodePortkubectl get all -n mini-project
kubectl port-forward svc/my-app-svc 8080:80 -n mini-project
curl http://localhost:8080kubectl set image deployment/my-app nginx=nginx:1.26 -n mini-project
kubectl rollout status deployment/my-app -n mini-project# Set a bad image
kubectl set image deployment/my-app nginx=nginx:thisdoesnotexist -n mini-project
# Debug it
kubectl get pods -n mini-project
kubectl describe pod <pod-name> -n mini-project
# Roll back
kubectl rollout undo deployment/my-app -n mini-project
kubectl rollout status deployment/my-app -n mini-project- All 4 manifests applied and app is reachable
- Rolling update completed successfully
- Deliberately broken, debugged, and rolled back
- Everything committed to
day-10/ - Write a short
day-10/notes.md— what was the hardest part? What clicked?
Update this as you go:
| Day | Topic | Status | Notes |
|---|---|---|---|
| 1 | Why kubectl exists | ⬜ Not started | |
| 2 | First commands | ⬜ Not started | |
| 3 | Pods | ⬜ Not started | |
| 4 | Deployments | ⬜ Not started | |
| 5 | Services | ⬜ Not started | |
| 6 | Config & Secrets | ⬜ Not started | |
| 7 | Debugging | ⬜ Not started | |
| 8 | YAML mastery | ⬜ Not started | |
| 9 | Multi-environment | ⬜ Not started | |
| 10 | Mini project | ⬜ Not started |
Replace ⬜ with ✅ as you complete each day.
- ✅ Confident with kubectl in daily use
- ✅ Understand Kubernetes primitives (Pod, Deployment, Service, ConfigMap, Secret)
- ✅ Debug real production issues systematically
- ✅ Manage apps across multiple environments
- ✅ Write clean, production-ready YAML
- ✅ Ready for real Kubernetes work
| Resource | Link |
|---|---|
| Official kubectl docs | https://kubernetes.io/docs/reference/kubectl/ |
| kubectl cheat sheet | https://kubernetes.io/docs/reference/kubectl/cheatsheet/ |
| Kubernetes concepts | https://kubernetes.io/docs/concepts/ |
| Minikube getting started | https://minikube.sigs.k8s.io/docs/start/ |
| Kind quickstart | https://kind.sigs.k8s.io/docs/user/quick-start/ |
| Interactive Kubernetes tutorial | https://kubernetes.io/docs/tutorials/kubernetes-basics/ |
| kubectx/kubens | https://github.com/ahmetb/kubectx |
# Get resources
kubectl get pods / nodes / deployments / svc / configmaps / secrets / all
# Inspect
kubectl describe <resource> <name>
kubectl logs <pod-name> [-f] [--previous]
kubectl exec -it <pod-name> -- /bin/bash
# Apply & manage
kubectl apply -f <file.yaml>
kubectl delete -f <file.yaml>
kubectl delete pod/svc/deployment <name>
# Deployments
kubectl scale deployment <name> --replicas=<n>
kubectl set image deployment/<name> <container>=<image>:<tag>
kubectl rollout status / history / undo deployment/<name>
# Namespaces & contexts
kubectl get all -n <namespace>
kubectl config use-context <name>
kubectl config set-context --current --namespace=<ns>
# Debug
kubectl get events --sort-by=.lastTimestamp
kubectl run debug --image=busybox --rm -it --restart=Never -- sh
kubectl apply -f file.yaml --dry-run=clientGood luck! The best way to learn kubectl is to break things, debug them, and fix them. This repo is your record of doing exactly that.
If this roadmap helped you, please consider starring this repository — it helps others find it and keeps the motivation going!
This project is open source and available under the MIT License.
|
👨🏻💻CS Engineer | AWS & DevOps Specialist -🎯focused on building reliable, observable, and scalable systems. |
