Kubernetes Basics for Developers
You've mastered Docker — containerizing apps, writing Dockerfiles, spinning up services with docker-compose. But what happens when you have dozens of containers spread across multiple servers that need to communicate, auto-scale, and recover from failures without manual intervention? That's exactly where Kubernetes steps in. Kubernetes (often abbreviated as K8s) is the de-facto standard for container orchestration, trusted by companies ranging from startups to Fortune 500 enterprises. In this guide, you'll learn the core concepts every developer needs to confidently work with Kubernetes: what it is, how it's architected, and how to deploy, expose, and scale your first application.
Kubernetes was born inside Google, inspired by an internal cluster management system called Borg that handled billions of container launches per week. Google open-sourced it in 2014 and donated it to the Cloud Native Computing Foundation (CNCF). Today it has over 100,000 GitHub stars and powers production workloads at Netflix, Airbnb, Spotify, and thousands of other companies.
What is Kubernetes and Why You Need It
Kubernetes is an open-source platform that automates the deployment, scaling, and management of containerized applications. Think of it as an operating system for your data center — you tell Kubernetes what you want (3 replicas of my API, always), and it figures out how to achieve and maintain that state across your fleet of servers.
Without container orchestration, teams face several painful problems as they scale:
- Manual scaling: Spinning up additional containers by hand when load spikes is slow and error-prone.
- No self-healing: When a container crashes, someone has to notice and restart it manually.
- Service discovery: Containers have ephemeral IP addresses — how do other services find them?
- Rolling deployments: Updating an application without downtime across many servers is complex.
- Resource waste: Knowing how to efficiently bin-pack workloads onto servers takes significant effort.
Kubernetes solves all of these. Its core value proposition for developers can be summarized as:
- Self-healing: Automatically restarts failed containers and reschedules on healthy nodes.
- Horizontal scaling: Scale workloads up or down with a single command or automatically based on CPU/memory.
- Service discovery & load balancing: Stable DNS names and IPs for your services regardless of which pods are running.
- Automated rollouts and rollbacks: Deploy new versions progressively; roll back if health checks fail.
- Secret and config management: Inject environment-specific config and secrets without rebuilding images.
- Storage orchestration: Auto-mount cloud storage (EBS, GCE PD) or local storage as needed.
Kubernetes Cluster Architecture: Control Plane manages Worker Nodes that run Pods containing your containers
Core Concepts: Clusters, Nodes, and Pods
Before writing a single YAML file, you need a mental model of Kubernetes' building blocks. These concepts nest inside each other like Russian dolls.
Cluster
A cluster is the top-level Kubernetes environment. It consists of a Control Plane (the "brain" that manages the cluster) and one or more Worker Nodes (the machines that run your actual application workloads). When you interact with Kubernetes via kubectl, you're talking to the cluster's API server.
Node
A node is a worker machine — a VM or a physical server. Every node runs three critical Kubernetes components:
- kubelet: An agent that communicates with the control plane and ensures containers are running as instructed.
- kube-proxy: Maintains network rules to allow communication to/from pods.
- Container runtime: The engine that actually runs containers — typically containerd or CRI-O.
Pod
The Pod is the smallest and most fundamental unit in Kubernetes. A pod wraps one or more tightly coupled containers that share the same network namespace (same IP address) and storage volumes. In practice, most pods run a single container. The key insight is that you never manage containers directly in Kubernetes — you manage pods.
apiVersion: v1
kind: Pod
metadata:
name: my-api-pod
labels:
app: my-api
spec:
containers:
- name: my-api
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "250m"
While you can create a Pod directly, you almost never should in production. Bare pods are not rescheduled if the node they're on dies. Always use a Deployment (or StatefulSet for stateful workloads) which gives you self-healing and rolling updates automatically.
Setting Up Your Local Environment
You need two tools to work with Kubernetes: kubectl (the CLI that talks to your cluster) and a local cluster to practice on. Minikube is the easiest option for a single-node local cluster.
Install kubectl
The Kubernetes command-line tool. This is how you deploy applications, inspect resources, and manage your cluster.
# Linux (amd64)
curl -LO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl && sudo mv kubectl /usr/local/bin/
# macOS (Homebrew)
brew install kubectl
# Windows (Chocolatey)
choco install kubernetes-cli
# Verify installation
kubectl version --client
Install Minikube
A single-node Kubernetes cluster that runs on your local machine. Perfect for development and learning.
# Linux
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
# macOS
brew install minikube
# Start a local Kubernetes cluster
minikube start
# Verify everything is working
kubectl cluster-info
kubectl get nodes
Explore the Kubernetes Dashboard
Minikube ships with the Kubernetes dashboard — a web UI to visualize your cluster state without memorizing kubectl commands.
# Opens the dashboard in your default browser
minikube dashboard
# Check what's running in the cluster
kubectl get all --all-namespaces
Deployments: Declaring Your Desired State
A Deployment is the standard way to run a stateless application in Kubernetes. You declare your desired state (which image to run, how many replicas, resource limits) and Kubernetes' controller continuously works to make reality match that declaration. This is the reconciliation loop at the heart of Kubernetes.
When you create a Deployment, Kubernetes automatically creates a ReplicaSet (which manages the actual pods). You rarely interact with ReplicaSets directly — Deployments manage them for you.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-api
labels:
app: my-api
spec:
replicas: 3
selector:
matchLabels:
app: my-api
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: my-api
spec:
containers:
- name: my-api
image: my-api:1.0.0
ports:
- containerPort: 3000
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "500m"
env:
- name: NODE_ENV
value: "production"
- name: PORT
value: "3000"
# Apply the deployment manifest
kubectl apply -f deployment.yaml
# Watch pods come up in real time
kubectl get pods -w
# Describe a specific pod (shows events, errors)
kubectl describe pod my-api-7d6f8b9c5-xk2p9
# Check deployment rollout status
kubectl rollout status deployment/my-api
# View deployment details
kubectl describe deployment my-api
# Stream logs from a pod
kubectl logs -f my-api-7d6f8b9c5-xk2p9
# Execute a command inside a running pod
kubectl exec -it my-api-7d6f8b9c5-xk2p9 -- /bin/sh
Rolling Updates and Rollbacks
One of Kubernetes' most valuable features is zero-downtime deployments. With the RollingUpdate strategy, Kubernetes brings up new pods before terminating old ones, ensuring your application stays available throughout a deployment.
# Deploy a new version by updating the image
kubectl set image deployment/my-api my-api=my-api:1.1.0
# Monitor the rollout
kubectl rollout status deployment/my-api
# View rollout history
kubectl rollout history deployment/my-api
# Rollback to previous version if something goes wrong
kubectl rollout undo deployment/my-api
# Rollback to a specific revision
kubectl rollout undo deployment/my-api --to-revision=2
Services: Stable Network Endpoints
Pods are ephemeral. They get new IP addresses every time they restart or are rescheduled to a different node. A Service gives you a stable virtual IP (ClusterIP) and DNS name that always routes traffic to the healthy pods behind it, regardless of which pods are currently running or what their IPs are.
A Kubernetes Service acts as a stable load-balanced endpoint in front of ephemeral Pods
Kubernetes offers four Service types for different networking scenarios:
| Service Type | Accessibility | Typical Use Case |
|---|---|---|
| ClusterIP (default) | Internal to cluster only | Microservice-to-microservice communication |
| NodePort | External via node IP + static port (30000–32767) | Local development, quick testing |
| LoadBalancer | External via cloud provider load balancer | Production external traffic (AWS ELB, GCP LB) |
| ExternalName | Maps service to external DNS name | Accessing external databases or third-party APIs |
apiVersion: v1
kind: Service
metadata:
name: my-api-service
spec:
# Selects pods with this label — must match Deployment's pod labels
selector:
app: my-api
type: LoadBalancer
ports:
- name: http
protocol: TCP
port: 80 # Port the service listens on
targetPort: 3000 # Port your container listens on
---
# For internal services between microservices, use ClusterIP:
apiVersion: v1
kind: Service
metadata:
name: database-service
spec:
selector:
app: postgres
type: ClusterIP
ports:
- port: 5432
targetPort: 5432
# Apply the service
kubectl apply -f service.yaml
# Check service status
kubectl get services
# On Minikube, get the service URL
minikube service my-api-service --url
# Forward a port for quick local testing (no service needed)
kubectl port-forward pod/my-api-7d6f8b9c5-xk2p9 8080:3000
ConfigMaps and Secrets
Hard-coding configuration values into your container images creates inflexible, insecure artifacts that can't be reused across environments. Kubernetes solves this with two primitives: ConfigMaps for non-sensitive configuration and Secrets for sensitive data.
Kubernetes Secrets are base64-encoded, not encrypted. Anyone with access to the cluster's etcd can read them. For production, always enable encryption at rest for etcd and consider using a dedicated secrets manager like HashiCorp Vault, AWS Secrets Manager, or Sealed Secrets.
Store non-sensitive environment-specific configuration:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
# Key-value pairs become environment variables
NODE_ENV: "production"
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
# Or mount entire config files
app.json: |
{
"port": 3000,
"timeout": 30,
"retries": 3
}
Create from the command line:
# From literal values
kubectl create configmap app-config \
--from-literal=NODE_ENV=production \
--from-literal=LOG_LEVEL=info
# From a file
kubectl create configmap app-config --from-file=app.json
# From a directory
kubectl create configmap app-config --from-file=./config/
Store sensitive data (base64-encoded in the manifest):
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
data:
# Values MUST be base64-encoded
# echo -n "mypassword" | base64
DB_PASSWORD: bXlwYXNzd29yZA==
API_KEY: c2VjcmV0YXBpa2V5MTIz
JWT_SECRET: bXlzdXBlcnNlY3JldGp3dA==
Create secrets without checking values into git:
# Create directly from literal (kubectl handles base64)
kubectl create secret generic app-secrets \
--from-literal=DB_PASSWORD=mypassword \
--from-literal=API_KEY=secretapikey123
# From a .env file (keep outside of git)
kubectl create secret generic app-secrets \
--from-env-file=.env.production
# View secret (base64-encoded)
kubectl get secret app-secrets -o yaml
# Decode a secret value
kubectl get secret app-secrets \
-o jsonpath='{.data.DB_PASSWORD}' | base64 --decode
Inject config and secrets into your pod spec:
spec:
containers:
- name: my-api
image: my-api:1.0.0
# Load all keys from ConfigMap as env vars
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: app-secrets
# Or load specific keys
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: DB_PASSWORD
# Mount ConfigMap as a volume (for config files)
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: app-config
Scaling and Health Checks
Kubernetes excels at scaling. You can scale manually with a single command or set up automatic scaling based on CPU or memory utilization. But before Kubernetes will route traffic to a pod, that pod needs to pass your health checks.
Manual and Automatic Scaling
# Scale to 5 replicas
kubectl scale deployment my-api --replicas=5
# Scale back down
kubectl scale deployment my-api --replicas=2
# Watch pods scale in real time
kubectl get pods -w
For automatic scaling, use a HorizontalPodAutoscaler (HPA). It monitors CPU/memory utilization and adjusts the replica count to keep utilization near your target:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-api
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
Liveness and Readiness Probes
Kubernetes uses two types of health checks to decide whether to route traffic to a pod and whether to restart it:
- Readiness Probe: Determines if a pod is ready to receive traffic. A failing readiness probe removes the pod from the Service's load balancer — no restarts, just traffic exclusion.
- Liveness Probe: Determines if a pod is still alive and functioning. A failing liveness probe triggers a pod restart. Use this to recover from deadlocks or corrupted state.
spec:
containers:
- name: my-api
image: my-api:1.0.0
ports:
- containerPort: 3000
livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 30 # Wait 30s after start before checking
periodSeconds: 10 # Check every 10 seconds
failureThreshold: 3 # Restart after 3 consecutive failures
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 10 # Start checking sooner than liveness
periodSeconds: 5
failureThreshold: 3
successThreshold: 1
Your /healthz (liveness) endpoint should only check that the process is alive — avoid checking external dependencies like databases. Your /ready (readiness) endpoint should check all dependencies: if the database connection pool is exhausted, return 503 so Kubernetes stops sending traffic until it recovers.
Essential kubectl Commands
You'll use these commands daily. Understanding them is more important than memorizing — use kubectl --help or kubectl explain <resource> liberally.
| Command | What It Does |
|---|---|
kubectl get pods |
List all pods in current namespace |
kubectl get pods -A |
List pods across all namespaces |
kubectl describe pod <name> |
Detailed pod info including events and errors |
kubectl logs <pod> -f |
Stream logs from a pod in real time |
kubectl exec -it <pod> -- sh |
Open an interactive shell inside a pod |
kubectl apply -f <file.yaml> |
Create or update resources from a YAML file |
kubectl delete -f <file.yaml> |
Delete resources defined in a YAML file |
kubectl get events --sort-by=.lastTimestamp |
View cluster events (great for debugging) |
kubectl top nodes |
Show CPU and memory usage per node |
kubectl top pods |
Show CPU and memory usage per pod |
kubectl explain deployment.spec |
Documentation for any resource field in-CLI |
kubectl config get-contexts |
List all configured clusters/contexts |
kubectl config use-context <name> |
Switch between clusters (dev, staging, prod) |
Namespaces: Logical Isolation Within a Cluster
Namespaces let you split a single cluster into isolated virtual clusters. They're used to separate teams, environments (dev/staging), or applications without provisioning separate clusters. Resources in different namespaces are isolated from each other by default.
# Create a namespace
kubectl create namespace my-team
# Deploy to a specific namespace
kubectl apply -f deployment.yaml -n my-team
# Set a default namespace for your session
kubectl config set-context --current --namespace=my-team
# Kubernetes ships with 4 built-in namespaces:
# default — where resources go if you don't specify
# kube-system — Kubernetes system components (don't touch)
# kube-public — readable by all, used for cluster info
# kube-node-lease — node heartbeat objects
Putting It All Together
Here's a minimal but production-quality manifest that combines a Deployment, Service, ConfigMap, and HPA into a single file — a realistic starting point for deploying any stateless web service:
---
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: production
data:
NODE_ENV: "production"
LOG_LEVEL: "warn"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-api
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: my-api
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: my-api
spec:
containers:
- name: my-api
image: my-api:1.0.0
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: app-config
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: my-api-service
namespace: production
spec:
selector:
app: my-api
type: LoadBalancer
ports:
- port: 80
targetPort: 3000
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-api-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-api
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Key Takeaways
- Pods are the atomic unit — but always manage them through Deployments, not directly.
- Deployments declare desired state and give you rolling updates, rollbacks, and self-healing for free.
- Services provide stable network endpoints for your ephemeral pods — use ClusterIP internally, LoadBalancer externally.
- ConfigMaps and Secrets decouple configuration from code — never bake env-specific values into images.
- Health probes (readiness + liveness) are non-negotiable in production — without them, Kubernetes can't route traffic safely or recover from failures.
- HPA automates scaling based on real metrics — set resource
requestscorrectly or HPA won't work as expected. - Start with Minikube locally, then move to a managed service like EKS (AWS), GKE (GCP), or AKS (Azure) for production.
"Kubernetes doesn't just run containers — it encodes your operational knowledge into machine-executable policies. Define it once, enforce it everywhere."
Kubernetes has a steep initial learning curve, but once the mental model clicks — desired state, reconciliation loops, labels & selectors — everything else falls into place. The investment pays off exponentially as your applications grow in complexity and traffic. Start small: deploy a single app with a Deployment and Service on Minikube, add health checks, try scaling, trigger a rollback. By the time you've done that end-to-end, you'll understand 80% of what you need for day-to-day Kubernetes work.