Learn kubectl from absolute zero to production-ready confidence in 10 focused days. Each day has a clear goal, key concepts, commands to practice, and a checkpoint task to commit to your repo.
You don't need any Kubernetes experience. You just need:
cd, ls, cat)| Option | Description | Link |
|---|---|---|
| Minikube | Runs locally on your machine | minikube.sigs.k8s.io |
| Kind | Kubernetes in Docker | kind.sigs.k8s.io |
| k3s | Lightweight, great for VMs | k3s.io |
| Play with K8s | Free browser-based cluster | play-with-k8s.com |
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
kubectl run, kubectl delete)kubectl apply -f file.yaml)# 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 --client
# Start a local cluster (choose one)
minikube start # Minikube
kind create cluster # Kind
# Verify cluster access
kubectl cluster-info
kubectl get nodes
kubectl version --client shows outputkubectl get nodes shows a Ready nodeday-01/notes.md: explain what is the API server and why kubectl talks to it# 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.template
Namespaces 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 # everything
A 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
kubectl get all -n kube-system and understand what you seekubectl explain on at least 3 different resourcesday-02/notes.md: What's the difference between get and describe?| 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 |
apiVersion: v1
kind: Pod
metadata:
name: my-pod
labels:
app: demo
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
# Apply the manifest
kubectl apply -f day-03/my-first-pod.yaml
# Logs & exec
kubectl logs my-pod
kubectl logs my-pod -f # follow live
kubectl logs my-pod --previous # last crashed container
kubectl exec -it my-pod -- /bin/bash
# Clean up
kubectl delete pod my-pod
day-03/day-03/notes.md: Why are pods ephemeral, and why does that matter?| 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. A ReplicaSet ensures N copies of a pod always run; a Deployment manages ReplicaSets and handles updates.
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
# Scale
kubectl scale deployment my-app --replicas=5
# Rolling update
kubectl set image deployment/my-app nginx=nginx:1.26
kubectl rollout status deployment/my-app
kubectl rollout history deployment/my-app
# Rollback
kubectl rollout undo deployment/my-app
kubectl rollout undo deployment/my-app --to-revision=1
kubectl rollout statusday-04/deployment.yamlPods 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 IP + port (30000-32767) | Local dev / testing |
LoadBalancer | External IP via cloud load balancer | Production on cloud |
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: NodePort
kubectl apply -f day-05/service.yaml
# On Minikube
minikube service my-app-svc --url
# Port-forward (works on any cluster)
kubectl port-forward svc/my-app-svc 8080:80
# Open http://localhost:8080
curlday-05/service.yamlday-05/notes.md: What would happen if a pod's label didn't match the service selector?Store non-sensitive config (URLs, feature flags, settings).
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). Values are base64-encoded — but can be encrypted at rest.
kubectl create secret generic db-secret \
--from-literal=DB_PASSWORD=supersecret \
--from-literal=API_KEY=myapikey123
# Decode a value
kubectl get secret db-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
env:
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: app-config
key: APP_ENV
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: DB_PASSWORD
kubectl exec -it <pod> -- env | grep APP_ENVday-06/configmap.yaml (never commit real secrets!)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?
| Error | Common Causes | Debug Command |
|---|---|---|
CrashLoopBackOff | App crash, bad config, missing env vars | kubectl logs <pod> --previous |
ImagePullBackOff | Wrong image name/tag, private registry | kubectl describe pod <pod> |
Pending | Insufficient resources, node selector mismatch | kubectl describe pod <pod> |
OOMKilled | Memory limit too low, memory leak | kubectl describe pod <pod> → Last State |
# Watch pods in real time
kubectl get pods -w
# Get events for the whole namespace
kubectl get events --sort-by=.lastTimestamp
# 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
nginx:doesnotexist)describe, logs, and eventsday-07/notes.md: Walk through your debug session step by stepapiVersion: apps/v1 # API group + version
kind: Deployment # resource type
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 |
resources:
requests: # minimum guaranteed
cpu: "100m" # 100 millicores = 0.1 CPU
memory: "128Mi"
limits: # maximum allowed
cpu: "500m"
memory: "256Mi"
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 — validate without applying
kubectl apply -f my-file.yaml --dry-run=client
kubectl apply -f my-file.yaml --dry-run=server
# See what would change
kubectl diff -f my-file.yaml
--dry-run=client before applyingday-08/deployment-with-probes.yaml# 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 config get-contexts
kubectl config use-context my-prod-cluster
kubectl config set-context --current --namespace=dev
kubectl config rename-context old-name new-name
kubectx and kubens for much faster context and namespace switching:brew install kubectxkubectx my-prod — switch clusterkubens dev — switch namespace
dev and staging namespacesday-09/notes.md: How would you prevent accidental kubectl delete in production?nginx (web server)
├── Deployment (3 replicas)
├── Service (NodePort)
├── ConfigMap (app settings)
└── Secret (fake API key)
kubectl create namespace mini-project
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_NAME: "My kubectl App"
LOG_LEVEL: "info"
apiVersion: v1
kind: Secret
metadata:
name: app-secret
type: Opaque
data:
API_KEY: bXlzZWNyZXRrZXkxMjM= # base64 of "mysecretkey123"
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: 5
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 80
type: NodePort
kubectl apply -f day-10/ -n mini-project
kubectl get all -n mini-project
kubectl port-forward svc/my-app-svc 8080:80 -n mini-project
curl http://localhost:8080
kubectl 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
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
day-10/day-10/notes.md: What was the hardest part? What clicked?# 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=client