0x55aa
← Back to Blog

Kubernetes for the Confused Developer: It's Docker on Steroids, Not Rocket Science πŸš€β˜ΈοΈ

β€’16 min read

Kubernetes for the Confused Developer: It's Docker on Steroids, Not Rocket Science πŸš€β˜ΈοΈ

Real confession: I avoided Kubernetes for 2 years. Why? Because every tutorial started with "First, understand pods, deployments, services, ingress controllers, persistent volumes, secrets, config maps, namespaces, and the control plane architecture..." My brain would shut down after "pods." 😡

Me: "I just want to deploy my Node.js app!"

K8s Expert: "Sure! Just write 47 YAML files, understand distributed systems, and master a CLI that makes Git look friendly!"

Me: "...I'll stick with Docker Compose."

But then I joined a team running production on Kubernetes. Forced to learn it. And you know what? After the initial pain, it's actually BRILLIANT. Let me save you the 3 weeks of confusion I went through!

What Even IS Kubernetes? (The 5-Year-Old Explanation) πŸ€”

Docker without Kubernetes:

You: "Hey Docker, run my app!"
Docker: "Sure! Running on port 3000!"
App crashes...
Docker: "Uh... it's dead. Want me to do something?"
You: "...guess I'll manually restart it."

Docker WITH Kubernetes:

You: "Hey Kubernetes, run 3 copies of my app. If one dies, restart it. Load balance between them. Update without downtime."
Kubernetes: "On it! Also, I'll monitor them, auto-scale based on CPU, and self-heal if anything breaks."
You: "...why didn't I learn this sooner?"

Translation: Kubernetes = A robot that manages your Docker containers, keeps them running, and doesn't need sleep! πŸ€–

The "Oh Crap" Moment That Made Me Learn K8s πŸ’€

Monday morning, my first week on the new team:

# Legacy deployment (what I was used to)
ssh production-server
docker-compose up -d
# Done! βœ…

# New team's deployment
kubectl apply -f deployment.yaml
# Output:
# Error: pods "myapp-7d4f6b8c5d-x7k9m" is forbidden
# Error: cannot allocate memory
# Error: crashloopbackoff
# Error: ImagePullBackOff
# Me: 😱😱😱

My boss: "Did the deploy work?"

Me looking at cryptic errors: "Uh... define 'work'?"

The reality: I had spent 7 years deploying Laravel and Node.js apps without Kubernetes. Felt like a senior dev. Then K8s made me feel like an intern again!

But after setting up our serverless infrastructure and containerizing dozens of services, I finally GET it. Let me explain it in a way that doesn't require a PhD!

Kubernetes Concepts (The "Oh That's What It Means!" Version) πŸ“š

Pods: Your App + Wrapper πŸ“¦

What everyone says: "A pod is the smallest deployable unit in Kubernetes that can contain one or more containers."

What it ACTUALLY means:

Pod = Your Docker container + Kubernetes management wrapper

Think of a pod like a box:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Pod            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚Your Dockerβ”‚  β”‚
β”‚  β”‚Container  β”‚  β”‚
β”‚  β”‚(Node.js)  β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Kubernetes manages the BOX, not the container directly!

Real example:

# pod.yaml - The simplest thing that runs
apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  containers:
  - name: app
    image: myapp:latest
    ports:
    - containerPort: 3000

Why pods exist: Kubernetes needs metadata (health checks, restart policies, resource limits). Pods provide that wrapper!

Deployments: The "Keep It Running" Manager πŸ”„

The problem with pods:

Create pod β†’ It runs β†’ It crashes β†’ It stays dead πŸ’€

You: "Kubernetes, why didn't you restart it?"
K8s: "You created a Pod. Pods are mortal. They die."
You: "...that's dark."

The solution - Deployments:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3  # Run 3 copies!
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: app
        image: myapp:latest
        ports:
        - containerPort: 3000

What Deployment does:

Deployment watches your pods:

Pod 1: Running βœ…
Pod 2: Running βœ…
Pod 3: CRASHED! πŸ’₯

Deployment: "Hold up! You wanted 3 replicas!"
*Creates new Pod 3*
Pod 3: Running βœ…

Deployment: "Balance restored. ✨"

After countless production deploys, I learned: NEVER create naked pods! Always use Deployments!

Services: The Load Balancer 🌐

The problem:

You have 3 pods:
Pod 1: IP 10.0.1.5
Pod 2: IP 10.0.2.8
Pod 3: IP 10.0.3.2

Pod crashes β†’ New pod gets NEW IP!

Your app: "Which IP do I connect to?!" 😡

The solution - Service:

apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
  ports:
  - port: 80
    targetPort: 3000
  type: LoadBalancer

What Service does:

Service gives you ONE stable address:
myapp-service:80

Internally it routes to:
β”œβ”€ Pod 1 (10.0.1.5:3000)
β”œβ”€ Pod 2 (10.0.2.8:3000)
└─ Pod 3 (10.0.3.2:3000)

Pods come and go? Service doesn't care!
Always routes to healthy pods! βœ…

Think of it like: Service = Stable domain name for your pods!

ConfigMaps & Secrets: Configuration That Doesn't Suck πŸ”

The old way (bad!):

# Hardcoded in Docker image
ENV DATABASE_URL="postgresql://db.prod.com/mydb"
ENV API_KEY="super-secret-key"

# Problems:
# - Secrets in image! 😱
# - Change config = rebuild image
# - Same image can't work in dev/staging/prod

The Kubernetes way:

# configmap.yaml - Non-sensitive config
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
data:
  DATABASE_HOST: "db.prod.com"
  LOG_LEVEL: "info"

---
# secret.yaml - Sensitive data (base64 encoded)
apiVersion: v1
kind: Secret
metadata:
  name: myapp-secrets
type: Opaque
data:
  DATABASE_PASSWORD: c3VwZXJzZWNyZXQxMjM=  # base64 encoded
  API_KEY: YXBpa2V5Zm9vcmVhbA==

Using them in deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
      - name: app
        image: myapp:latest
        env:
        # From ConfigMap
        - name: DATABASE_HOST
          valueFrom:
            configMapKeyRef:
              name: myapp-config
              key: DATABASE_HOST

        # From Secret
        - name: DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: myapp-secrets
              key: DATABASE_PASSWORD

Why this is brilliant:

  • βœ… Same image works everywhere (dev/staging/prod)
  • βœ… Change config without rebuilding
  • βœ… Secrets are encrypted at rest
  • βœ… Can update config while app runs!

Docker taught me the hard way: Never bake secrets into images. Kubernetes makes it easy to do it right!

My First Real Kubernetes Deployment (The Learning Journey) πŸŽ“

Let me show you a COMPLETE working example - the Node.js API I deployed last month:

Step 1: Containerize the App (You Already Know This!)

# Dockerfile - Nothing special, just good Docker
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
USER node
CMD ["node", "dist/index.js"]
# Build and push to registry
docker build -t myregistry.io/myapp:v1.0.0 .
docker push myregistry.io/myapp:v1.0.0

Step 2: Create Kubernetes YAML Files

deployment.yaml - The main config:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 3  # Start with 3 instances
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: app
        image: myregistry.io/myapp:v1.0.0
        ports:
        - containerPort: 3000

        # Resource limits (IMPORTANT!)
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"

        # Health checks (CRITICAL!)
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 10
          periodSeconds: 5

        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 3

        # Environment variables
        env:
        - name: NODE_ENV
          value: "production"
        - name: PORT
          value: "3000"
        - name: DATABASE_HOST
          valueFrom:
            configMapKeyRef:
              name: myapp-config
              key: DATABASE_HOST
        - name: DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: myapp-secrets
              key: DATABASE_PASSWORD

service.yaml - Expose the app:

apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp  # Routes to pods with this label
  ports:
  - protocol: TCP
    port: 80       # External port
    targetPort: 3000  # Pod port
  type: LoadBalancer  # Creates cloud load balancer

configmap.yaml - Non-sensitive config:

apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
data:
  DATABASE_HOST: "postgres.default.svc.cluster.local"
  DATABASE_PORT: "5432"
  DATABASE_NAME: "myapp_prod"
  LOG_LEVEL: "info"
  REDIS_HOST: "redis.default.svc.cluster.local"

secret.yaml - Sensitive data:

apiVersion: v1
kind: Secret
metadata:
  name: myapp-secrets
type: Opaque
stringData:  # stringData auto-encodes to base64
  DATABASE_PASSWORD: "my-super-secret-password"
  REDIS_PASSWORD: "redis-secret"
  JWT_SECRET: "jwt-signing-key"

Step 3: Deploy Everything!

# Apply all configs
kubectl apply -f configmap.yaml
kubectl apply -f secret.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

# Watch it come up
kubectl get pods -w

# Output:
# NAME                     READY   STATUS    RESTARTS   AGE
# myapp-7d4f6b8c5d-abc12   0/1     Pending   0          1s
# myapp-7d4f6b8c5d-abc12   0/1     ContainerCreating   0          2s
# myapp-7d4f6b8c5d-abc12   1/1     Running             0          15s
# myapp-7d4f6b8c5d-xyz34   1/1     Running             0          15s
# myapp-7d4f6b8c5d-def56   1/1     Running             0          15s

# Check the service
kubectl get service myapp-service

# Output:
# NAME            TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)
# myapp-service   LoadBalancer   10.96.45.123    35.123.45.67    80:31234/TCP

# Your app is now live at 35.123.45.67! πŸŽ‰

The kubectl Commands You'll Actually Use πŸ› οΈ

View everything:

# See all pods
kubectl get pods

# See all services
kubectl get services

# See all deployments
kubectl get deployments

# See EVERYTHING at once
kubectl get all

Check pod details:

# Describe a pod (super detailed!)
kubectl describe pod myapp-7d4f6b8c5d-abc12

# View pod logs
kubectl logs myapp-7d4f6b8c5d-abc12

# Follow logs (like tail -f)
kubectl logs -f myapp-7d4f6b8c5d-abc12

# Logs from ALL pods with label
kubectl logs -l app=myapp --tail=100

Debug pods:

# Get a shell inside a pod
kubectl exec -it myapp-7d4f6b8c5d-abc12 -- /bin/sh

# Run a one-off command
kubectl exec myapp-7d4f6b8c5d-abc12 -- env

# Port forward to test locally
kubectl port-forward myapp-7d4f6b8c5d-abc12 3000:3000
# Now access at localhost:3000!

Update deployments:

# Update image version
kubectl set image deployment/myapp app=myregistry.io/myapp:v2.0.0

# Watch rollout
kubectl rollout status deployment/myapp

# Rollback if broken!
kubectl rollout undo deployment/myapp

# Scale up/down
kubectl scale deployment/myapp --replicas=5

Delete stuff:

# Delete a pod (deployment will recreate it!)
kubectl delete pod myapp-7d4f6b8c5d-abc12

# Delete deployment (and all its pods)
kubectl delete deployment myapp

# Delete everything from YAML
kubectl delete -f deployment.yaml

# Nuclear option - delete EVERYTHING
kubectl delete all --all  # ☒️ CAREFUL!

Common Kubernetes WTF Moments (And How to Fix Them) 🀦

WTF #1: ImagePullBackOff

kubectl get pods
# NAME                     READY   STATUS             RESTARTS   AGE
# myapp-abc123             0/1     ImagePullBackOff   0          2m

kubectl describe pod myapp-abc123
# Events:
# Failed to pull image "myapp:latest": rpc error: code = Unknown desc = Error response from daemon: pull access denied

What it means: Kubernetes can't download your Docker image!

Common causes:

# 1. Image doesn't exist
docker images | grep myapp
# Fix: Build and push the image!

# 2. Wrong image name in deployment
# Fix: Check image name in deployment.yaml

# 3. Private registry without auth
# Fix: Create image pull secret
kubectl create secret docker-registry regcred \
  --docker-server=myregistry.io \
  --docker-username=myuser \
  --docker-password=mypass

# Add to deployment:
spec:
  template:
    spec:
      imagePullSecrets:
      - name: regcred

WTF #2: CrashLoopBackOff

kubectl get pods
# NAME                     READY   STATUS             RESTARTS   AGE
# myapp-abc123             0/1     CrashLoopBackOff   5          3m

What it means: Your app keeps crashing! Kubernetes keeps restarting it!

Debug it:

# Check logs
kubectl logs myapp-abc123

# Common issues I've seen:
# - Missing environment variables
# - Can't connect to database
# - Syntax error in code
# - Port already in use

# Check events
kubectl describe pod myapp-abc123 | grep -A 10 Events

My debugging story: Spent 2 hours debugging CrashLoopBackOff. Turned out I had PORT=3000 in ConfigMap but app was reading process.env.APP_PORT. Classic! 🀦

WTF #3: Pending Pods Forever

kubectl get pods
# NAME                     READY   STATUS    RESTARTS   AGE
# myapp-abc123             0/1     Pending   0          10m
# Still pending after 10 minutes?! 😱

What it means: Kubernetes can't schedule your pod!

Common causes:

# Check why it's pending
kubectl describe pod myapp-abc123

# Reason 1: Not enough resources
# Events: 0/3 nodes available: insufficient memory

# Fix: Reduce resource requests or add nodes

# Reason 2: No nodes match selector
# Fix: Remove node selector or add matching node

# Reason 3: Persistent volume not available
# Fix: Check PVC status

WTF #4: Service Not Accessible

# Service created
kubectl get service myapp-service
# NAME            TYPE           CLUSTER-IP      EXTERNAL-IP
# myapp-service   LoadBalancer   10.96.45.123    <pending>

# External IP stuck at <pending>! 😱

What it means: Load balancer isn't being created!

Fixes:

# On cloud (AWS/GCP/Azure): Should work automatically
# If stuck, check cloud provider quotas

# On local (minikube/kind): Use NodePort instead
spec:
  type: NodePort  # Instead of LoadBalancer

# Or use port-forward
kubectl port-forward service/myapp-service 8080:80

# On bare metal: Use MetalLB or nginx-ingress

The "I Finally Get It!" Moment πŸ’‘

When it clicked for me:

Docker Compose:
docker-compose.yml β†’ 1 server β†’ Your app runs

Problem: Server dies = app dies
Kubernetes:
deployment.yaml β†’ Multiple servers (cluster)
                β†’ Pods spread across servers
                β†’ One server dies? Pods move to healthy servers!
                β†’ App keeps running! βœ…

That's when I got it:
Kubernetes = Docker Compose for a CLUSTER of servers!

The magic:

# Delete a pod (simulate crash)
kubectl delete pod myapp-abc123

# Watch what happens
kubectl get pods -w

# Output:
# myapp-abc123   1/1   Terminating   0   5m
# myapp-xyz789   0/1   Pending       0   0s  ← NEW POD!
# myapp-xyz789   0/1   Running       0   2s
# myapp-xyz789   1/1   Running       0   5s

# Deployment saw pod die, created replacement!
# Your app never went down! πŸŽ‰

After architecting on AWS, I realized: This is what auto-scaling groups do! But Kubernetes is SO much more powerful!

Real Production Lessons (From My Pain) πŸ’’

Lesson #1: Always Set Resource Limits!

What I did (bad):

containers:
- name: app
  image: myapp:latest
  # No resource limits! 😱

What happened:

One pod had memory leak β†’ Used ALL node memory
Other pods starved β†’ Entire node crashed πŸ’₯
Kubernetes moved pods β†’ New node also crashed
Cascading failure! πŸ”₯πŸ”₯πŸ”₯

What I do now (good):

containers:
- name: app
  image: myapp:latest
  resources:
    requests:
      memory: "256Mi"  # Minimum needed
      cpu: "250m"
    limits:
      memory: "512Mi"  # Maximum allowed
      cpu: "500m"

Setting up CI/CD for our Kubernetes apps taught me: Resource limits aren't optional - they're CRITICAL!

Lesson #2: Health Checks Are Non-Negotiable!

Without health checks:

Pod starts β†’ Kubernetes sends traffic immediately
App still initializing β†’ Returns 500 errors
Users see errors! 😱

With proper health checks:

livenessProbe:
  httpGet:
    path: /health
    port: 3000
  initialDelaySeconds: 10  # Wait 10s before first check
  periodSeconds: 5         # Check every 5s

readinessProbe:
  httpGet:
    path: /ready
    port: 3000
  initialDelaySeconds: 5
  periodSeconds: 3

In your app:

// Liveness: "Is the app running?"
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'alive' });
});

// Readiness: "Is the app READY for traffic?"
let isReady = false;

app.get('/ready', (req, res) => {
  if (!isReady) {
    return res.status(503).json({ status: 'not ready' });
  }
  res.status(200).json({ status: 'ready' });
});

// Initialization
async function init() {
  await connectDatabase();
  await warmCache();
  isReady = true;
}

init();

Lesson #3: Use Namespaces to Organize!

Without namespaces:

kubectl get pods
# myapp-dev-abc123
# myapp-staging-xyz789
# myapp-prod-def456
# database-dev-ghi789
# redis-staging-jkl012
# ALL MIXED TOGETHER! 😡

With namespaces:

# Create namespaces
kubectl create namespace dev
kubectl create namespace staging
kubectl create namespace prod

# Deploy to specific namespace
kubectl apply -f deployment.yaml -n prod

# View by namespace
kubectl get pods -n prod
# Only production pods! βœ…

# Switch default namespace
kubectl config set-context --current --namespace=prod

The Kubernetes Learning Roadmap πŸ—ΊοΈ

Week 1: The Basics

  • βœ… Understand pods, deployments, services
  • βœ… Deploy a simple app
  • βœ… Learn kubectl basics
  • βœ… Set up health checks

Week 2: Configuration

  • βœ… Use ConfigMaps and Secrets
  • βœ… Set resource limits
  • βœ… Learn namespaces
  • βœ… Understand labels and selectors

Week 3: Debugging

  • βœ… Read logs effectively
  • βœ… Describe pods and events
  • βœ… Port forwarding for testing
  • βœ… Exec into pods

Month 2: Advanced Topics

  • βœ… Persistent Volumes (databases)
  • βœ… Ingress controllers (routing)
  • βœ… Horizontal Pod Autoscaling
  • βœ… Network policies

You don't need to learn it all at once! Start with deployments + services. Add complexity as needed!

The Bottom Line πŸ’‘

Kubernetes isn't rocket science - it's just Docker with a really good babysitter!

What Kubernetes does for you:

  1. Keeps apps running - Restarts crashed pods automatically
  2. Load balances - Distributes traffic across pods
  3. Scales easily - Add/remove replicas with one command
  4. Updates safely - Rolling deploys with zero downtime
  5. Self-heals - Moves pods from unhealthy nodes
  6. Manages config - ConfigMaps and Secrets done right

What you learned today:

  1. Pods = Your containers + Kubernetes wrapper
  2. Deployments = Keep pods running (use these, not naked pods!)
  3. Services = Stable address for your pods
  4. ConfigMaps/Secrets = Configuration that doesn't suck
  5. kubectl = Your new best friend
  6. Resource limits = ALWAYS set them!
  7. Health checks = Non-negotiable!

The truth: After deploying to production on Kubernetes, going back to manually managing Docker containers feels like going back to horse-drawn carriages after driving a Tesla! πŸš—βš‘

You don't need a massive cluster to start! Minikube on your laptop is enough to learn! Start small, deploy one app, and grow from there!

Your Action Plan πŸš€

This weekend:

  1. Install minikube or kind
  2. Deploy the example app from this post
  3. Practice kubectl commands
  4. Break things and fix them!

This month:

  1. Containerize your current project
  2. Write deployment YAML
  3. Deploy to a test cluster
  4. Set up proper health checks and resource limits

This quarter:

  1. Move staging environment to Kubernetes
  2. Learn auto-scaling
  3. Set up monitoring (Prometheus + Grafana)
  4. Deploy to production with confidence!

Resources You Actually Need πŸ“š

Official docs:

Local Kubernetes:

  • minikube - Full Kubernetes on your laptop
  • kind - Kubernetes in Docker

Cloud Kubernetes:

  • AWS EKS, Google GKE, Azure AKS - Managed Kubernetes

Tools I use:

Real talk: The official docs are actually good! Unlike most docs! Start there!


Ready to level up your deployment game? Connect with me on LinkedIn and share your first K8s deployment!

Want to see production configs? Check out my GitHub - real Kubernetes YAML from real projects!

Now go forth and orchestrate those containers! ☸️✨


P.S. If you're still manually SSH-ing into servers to restart apps, Kubernetes will change your life. Seriously. It's like having a DevOps team that works 24/7 and never sleeps!

P.P.S. I once spent 4 hours debugging a deployment. The problem? A typo in the YAML indentation. Kubernetes cares about spaces like Python cares about indentation. You've been warned! πŸ˜