From a single VM to a cluster: pods, deployments, services, configmaps, secrets — on your laptop.
Shipped to one VM. docker-compose handled multi-container. Terraform built the VM.
Shifts to a cluster. K8s replaces docker-compose + the VM. Same app, different runtime.
“You’re not learning K8s to use K8s. You’re learning K8s because production wants more than one box.”
replicas: N and apply.One or more containers sharing a network + lifecycle. The smallest deployable unit.
Manages replicas of a pod. Handles rolling updates and rollbacks.
Stable virtual IP + DNS name in front of N pods. The thing other pods talk to.
Config and credentials, mounted as env vars or files.
apiVersion: v1
kind: Pod
metadata:
name: api
labels: {app: shortener-api}
spec:
containers:
- name: api
image: ghcr.io/you/shortener-api:0.4.0
ports: [{containerPort: 8080}]
In practice you almost never write Pods directly — Deployments do it for you.
apiVersion: apps/v1
kind: Deployment
metadata: {name: shortener-api}
spec:
replicas: 2
selector: {matchLabels: {app: shortener-api}}
template:
metadata: {labels: {app: shortener-api}}
spec:
containers:
- name: api
image: ghcr.io/you/shortener-api:0.4.0
ports: [{containerPort: 8080}]
selector.matchLabels must match template.metadata.labels — silent breakage if it doesn’t.
apiVersion: v1
kind: Service
metadata: {name: shortener-api}
spec:
selector: {app: shortener-api}
ports: [{port: 80, targetPort: 8080}]
In-cluster DNS resolves shortener-api to this Service’s virtual IP — the worker pod calls http://shortener-api/.
apiVersion: v1
kind: ConfigMap
metadata: {name: shortener-config}
data:
LOG_LEVEL: info
WORKER_INTERVAL_S: "30"
---
apiVersion: v1
kind: Secret
metadata: {name: shortener-db}
type: Opaque
stringData:
DATABASE_URL: postgres://shortener:s3cr3t@postgres:5432/shortener
stringData lets you write plaintext (the cluster encodes it). For real secrets use external-secrets or sealed-secrets — covered later.
Am I alive? If false, K8s restarts the container.
Am I ready to serve? If false, the Service stops routing traffic to me.
livenessProbe:
httpGet: {path: /healthz, port: 8080}
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet: {path: /ready, port: 8080}
periodSeconds: 5
“Don’t point both probes at /. Liveness is for crash detection; readiness is for warmup + dependency checks. Wire them to different endpoints.”
kind — Kubernetes on your laptopbrew install kind kubectl
kind create cluster --name shortener
kubectl cluster-info --context kind-shortener
kind runs the control plane + nodes inside Docker containers; no cloud account required.
kubectl apply -f k8s/
kubectl get pods -w
kubectl logs deploy/shortener-api
kubectl describe pod <name>
kubectl port-forward svc/shortener-api 8080:80
apply is idempotent, describe is your first-stop for failures, logs and port-forward cover 80% of day-one debugging.
sample-app/k8s/ with a Namespace + Deployment + Service for the API.kind cluster and curl the API through kubectl port-forward.kind load docker-image (kind doesn’t pull local images by default unless you push to a registry).CrashLoopBackOff → kubectl logs and kubectl describe first.kubectl exec into the pod and curl the probe endpoint directly.kubectl describe svc <name> for the Endpoints: line.kind lets you exercise the full K8s surface on a laptop.