Kubernetes Guide for Docker Users
Kubernetes 1.31 from the ground up for developers who know Docker: pods, deployments, services, and how to ship your first app to a cluster.
Kubernetes 1.31 from the ground up for developers who know Docker: pods, deployments, services, and how to ship your first app to a cluster.
Kubernetes (k8s) is a container orchestration platform: it runs your Docker containers across a cluster of machines, handles restarts when things crash, scales replicas up and down, routes traffic, and manages secrets and config. If Docker answers “how do I run a container?”, Kubernetes answers “how do I run 50 containers across 10 machines reliably?” Kubernetes 1.31 is the current stable release. You already know the hard part — containers — so this guide focuses on the Kubernetes-specific concepts you need to ship something real.
# Install kubectl (the CLI)
brew install kubectl # macOS
# or
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl && sudo mv kubectl /usr/local/bin/
# Local cluster for development
brew install minikube && minikube start # macOS
# or use kind (Kubernetes in Docker)
brew install kind && kind create cluster
# Verify your cluster connection
kubectl cluster-info
kubectl get nodes
# Run a container — the Docker equivalent of `docker run nginx`
kubectl run mynginx --image=nginx --port=80
# Check it's running
kubectl get pods
# Stream logs — same as `docker logs -f`
kubectl logs -f mynginx
# Shell into a running pod — same as `docker exec -it`
kubectl exec -it mynginx -- /bin/bash
# Delete it
kubectl delete pod mynginx
In practice, you never use kubectl run for real workloads. You write YAML manifests and apply them. But kubectl run is useful for quick debugging.
| Concept | Docker equivalent | What it is |
|---|---|---|
| Pod | Container (roughly) | Smallest deployable unit — one or more containers that share a network and storage |
| Deployment | docker run + restart policy | Manages a set of identical pods, handles rolling updates |
| Service | Port mapping / reverse proxy | Stable network endpoint for a set of pods |
| Namespace | — | Virtual cluster for isolation — default is used if you don’t specify |
| ConfigMap | --env-file | Non-secret config, mounted as env vars or files |
| Secret | .env with secrets | Base64-encoded sensitive values (passwords, tokens) |
| Ingress | nginx reverse proxy | HTTP routing rules — maps hostnames/paths to Services |
| Node | Docker host | A machine (VM or bare metal) in the cluster |
The relationship that trips up Docker users: A Pod is not a container — it’s a wrapper around one or more containers that always land on the same machine and share localhost. Most Pods have one container. You define a Deployment that says “I want 3 replicas of this Pod”, and Kubernetes makes it so.
# List resources — works for any resource type
kubectl get pods
kubectl get deployments
kubectl get services
kubectl get all # everything in the default namespace
# With more detail
kubectl get pods -o wide # shows IP and node
kubectl get pods -o yaml # full YAML definition
kubectl describe pod mypod # human-readable detail + events
# Watch for changes in real time
kubectl get pods -w
# Apply a manifest (create or update)
kubectl apply -f deployment.yaml
# Apply everything in a directory
kubectl apply -f ./k8s/
# Delete what a manifest created
kubectl delete -f deployment.yaml
# Edit a live resource in your $EDITOR
kubectl edit deployment myapp
# Force restart all pods in a deployment (no config change)
kubectl rollout restart deployment/myapp
# Get logs — last 100 lines
kubectl logs deployment/myapp --tail=100
# Logs from a specific container in a multi-container pod
kubectl logs mypod -c sidecar-container
# Run a temporary debug pod in the cluster
kubectl run debug --image=busybox --rm -it --restart=Never -- sh
# Copy files to/from a pod
kubectl cp mypod:/app/logs/error.log ./error.log
# Port-forward to reach a pod or service locally
kubectl port-forward deployment/myapp 8080:80
kubectl port-forward service/myapp 8080:80
# List namespaces
kubectl get namespaces
# Run a command in a specific namespace
kubectl get pods -n kube-system
# Set a default namespace for your session
kubectl config set-context --current --namespace=myapp
This is the minimum viable Kubernetes setup for a stateless web app — a Deployment and a Service.
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myrepo/myapp:v1.2.0 # always use a specific tag, not :latest
ports:
- containerPort: 8080
env:
- name: PORT
value: "8080"
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "500m"
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp # matches pods with this label
ports:
- port: 80
targetPort: 8080
type: ClusterIP # internal-only; use LoadBalancer for cloud or Ingress for HTTP routing
kubectl apply -f deployment.yaml
kubectl get pods -w # watch pods come up
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp-config
data:
LOG_LEVEL: "info"
FEATURE_FLAG_DARK_MODE: "true"
---
# secret.yaml — values must be base64 encoded
apiVersion: v1
kind: Secret
metadata:
name: myapp-secrets
type: Opaque
data:
# echo -n "mysecretpassword" | base64
DB_PASSWORD: bXlzZWNyZXRwYXNzd29yZA==
# Reference them in your Deployment
spec:
containers:
- name: myapp
envFrom:
- configMapRef:
name: myapp-config # inject all ConfigMap keys as env vars
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-secrets
key: DB_PASSWORD
# Update the image — triggers a rolling update with zero downtime
kubectl set image deployment/myapp myapp=myrepo/myapp:v1.3.0
# Watch the rollout
kubectl rollout status deployment/myapp
# Something went wrong — roll back to the previous version
kubectl rollout undo deployment/myapp
# Roll back to a specific revision
kubectl rollout history deployment/myapp
kubectl rollout undo deployment/myapp --to-revision=2
Never use :latest in production. If a node restarts and pulls a new :latest image, you can end up with different versions running in the same deployment. Pin to a specific tag (v1.2.0) or image digest.
Resources matter more than you think. Without resources.requests, Kubernetes can’t make scheduling decisions, and your pods may end up on overloaded nodes. Without resources.limits, a runaway process can consume an entire node’s memory. Set both.
kubectl apply vs kubectl create: Use apply — it’s idempotent and works for both creating and updating. create fails if the resource already exists.
Readiness probes prevent bad deploys. Without a readinessProbe, Kubernetes sends traffic to pods the moment they start, before your app is actually ready. Define a health endpoint and use it.
Secrets are not actually secret by default. kubectl get secret myapp-secrets -o yaml will show you the base64-decoded values if you have RBAC access. Base64 is encoding, not encryption. For real secret management, integrate with Vault, AWS Secrets Manager, or use Kubernetes External Secrets Operator.
kubectl delete pod is not destructive. The Deployment controller immediately creates a replacement. If you want to reduce replicas, scale the deployment: kubectl scale deployment/myapp --replicas=2.
As of Kubernetes 1.25, PodSecurityPolicy is removed. If you’re following older tutorials that use PSP, they’re outdated. The replacement is Pod Security Admission (PSA) with labels on namespaces.
Kubeconfig contexts let you switch clusters. If you work with multiple clusters (dev, staging, prod):
# List contexts
kubectl config get-contexts
# Switch context
kubectl config use-context prod-cluster
# Or scope a single command with --context
kubectl get pods --context=staging-cluster
kubectl usage — brew install k9sSource: z2h.fyi/cheatsheets/kubernetes-guide — Zero to Hero cheatsheets for developers.