DEV Community

Timevolt
Timevolt

Posted on

Kubernetes Quest: From Localhost to Production (A Lord of the Rings Tale)

The Quest Begins (The "Why")

Honestly, I used to think “just run docker run and call it a day.” I’d spin up a container locally, test my API with curl, and feel like a wizard. Then the moment came when I needed to show that same magic to a teammate, or worse—push it to a real server where people actually depended on it. My container would start, but the port was wrong, the environment variables were missing, and the whole thing crashed harder than a hobbit trying to outrun a Nazgûl. I realized I needed something that could take my little Docker image and run it the same way everywhere—my laptop, a staging server, and finally a production cluster. That’s when I heard the call of Kubernetes, and I knew my adventure had begun.

The Revelation (The Insight)

The treasure I uncovered wasn’t some mysterious incantation; it was the idea of describing your desired state and letting Kubernetes figure out how to get there. Instead of SSH‑ing into a box and hoping the process stays alive, you write a declarative manifest (usually YAML) that says: “I want three copies of this image, listening on port 8080, with these environment variables, and I never want them to use more than 500 MiB of memory.” Kubernetes then continuously reconciles reality with that description—starting, stopping, scaling, and healing pods as needed. It’s like giving a faithful steward a map and letting them keep the kingdom running while you focus on building new features.

The biggest “aha!” moment for me was seeing how the same manifest works locally with Minikube (or Docker Desktop) and in a managed cloud service like GKE or EKS. No rewriting, no weird scripts—just kubectl apply -f my-app.yaml and watch the magic happen.

Wielding the Power (Code & Examples)

Let’s walk through a simple Node.js API. First, the Dockerfile (nothing fancy):

# Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Enter fullscreen mode Exit fullscreen mode

Build and run it locally (the “before”):

docker build -t my-api:latest .
docker run -d -p 3000:3000 --name my-api my-api:latest
curl http://localhost:3000/health
# → {"status":"ok"}
Enter fullscreen mode Exit fullscreen mode

That works, but it’s fragile. If I stop the container, the API disappears. If I need two instances for load, I’m manually juggling ports. Enter Kubernetes.

The Manifest (the “after”)

Create a file k8s/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-api
spec:
  replicas: 2                     # <-- we want two copies
  selector:
    matchLabels:
      app: my-api
  template:
    metadata:
      labels:
        app: my-api
    spec:
      containers:
        - name: api
          image: my-api:latest    # <-- same image we built
          ports:
            - containerPort: 3000
          env:
            - name: NODE_ENV
              value: "production"
          resources:
            limits:
              memory: "512Mi"
              cpu: "500m"
            requests:
              memory: "256Mi"
              cpu: "250m"
---
apiVersion: v1
kind: Service
metadata:
  name: my-api-svc
spec:
  selector:
    app: my-api
  ports:
    - protocol: TCP
      port: 80                    # exposed inside the cluster
      targetPort: 3000
  type: ClusterIP
Enter fullscreen mode Exit fullscreen mode

Apply it with Minikube (or any cluster you have access to):

# start a local cluster if you don’t have one
minikube start

# load your image into the cluster’s Docker daemon
minikube cache add my-api:latest

# apply the manifest
kubectl apply -f k8s/deployment.yaml

# check the pods
kubectl get pods
# NAME                     READY   STATUS    RESTARTS   AGE
# my-api-7c9d5f6b9-abcde   1/1     Running   0          10s
# my-api-7c9d5f6b9-fghij   1/1     Running   0          10s

# expose it locally for testing
kubectl port-forward svc/my-api-svc 8080:80
curl http://localhost:8080/health
# → {"status":"ok"}
Enter fullscreen mode Exit fullscreen mode

Boom! Two identical pods, load‑balanced by the service, and if one crashes Kubernetes automatically replaces it. No more babysitting.

Common Traps (the “gotchas”)

  1. Using :latest in production – It’s tempting, but if you rebuild and push a new :latest while the cluster is pulling, you might end up with mixed versions. Tag your images with a Git SHA or a version number (my-api:v1.2.3) and update the manifest accordingly.
  2. Forgetting to set resource limits – Without limits, a pod can gobble up all node memory, starving other workloads. Always define requests and limits; the scheduler uses them to place pods sensibly.
  3. Exposing the wrong port – The containerPort must match what your app actually listens on. If you expose 8080 in the service but your app runs on 3000, you’ll get connection refused. Double‑check the numbers.

Why This New Power Matters

Now that I speak Kubernetes, I can:

  • Move seamlessly from laptop to staging to production with the same YAML.
  • Scale on a whim—need ten instances for a traffic spike? kubectl scale deployment my-api --replicas=10.
  • Self‑heal—if a node dies, the controller reschedules the pods elsewhere.
  • Observability—hooks for Prometheus, Loki, or any tracing system just plug right in.

It’s like upgrading from a trusty sword to a lightsaber: same skill, far more reach, and a lot less chance of losing a limb to a stray bug.

The Journey Continues

Your turn! Grab any little service you’ve got running in a Docker container, write a simple Deployment + Service manifest, and try it out in Minikube (or a free tier cluster on your favorite cloud). See how it feels to watch Kubernetes keep your app alive while you go grab a coffee.

Challenge: Deploy your app, set up a horizontal pod autoscaler based on CPU usage, and then generate some load with hey or wrk. Watch the replica count climb—and then fall back when the load drops. Share your results or a screenshot in the comments; I’d love to hear how your quest went!

Happy clustering, and may your pods always be in the Running state. 🚀

Top comments (0)