The "Why" of Kubernetes

A code-dependent life form.
Search for a command to run...

A code-dependent life form.
The first time I held my phone against my passport and watched an app pull my own photo off the chip, I had two reactions. First: that's really cool. Second: wait, how did it do that? So I went diggin

I just finished rebuilding our docs at docs.self.xyz, and the whole thing came from one assumption: every page now gets read twice. Once by a developer, and once by the agent sitting next to them. If

The final part of a series on agent harnesses. We started with the model-plus-harness idea, then built subagents, gave them a safe place to work, and wired up tools, memory, and workflows. This last p

Part four of a series on agent harnesses. The earlier pieces covered the model-plus-harness idea, subagents, and sandboxes. This one is about the three things that turn a capable agent into one that e

Part three of a series on agent harnesses. Part one introduced the model-plus-harness idea, and part two split work across subagents. This piece is about the layer where agents stop just talking and s

Yes, I've always wanted to learn more about Kubernetes. I've had a long-standing passion for it. However, what is the reality? The inner workings of the system, including the pods, services, DNS, and how these tiny, unseen components enable large software to function like magic, have always captivated me more.
So this isn’t “just another Kubernetes guide.”
This is me, as a backend developer, sharing my journey of why each YAML line matters and how it fits together when you’re connecting a Python app to Redis inside Kubernetes.
I did this because:
Instead of merely deploying from ChatGPT or AI responses, I wanted to fully comprehend Kubernetes.
I was interested in the communication between pods of a basic application using Redis (any database).
That's all. No fancy objective. Just the curiosity of a backend nerd. :)
If you’re starting from scratch, here’s how I did it:
brew install minikube
brew install kubectl
minikube start
kubectl get nodes
You're all set if you see minikube Ready. This is the troubleshooting guide if not.
You are undoubtedly already aware of the hype surrounding Kubernetes if you are reading this. Let's face it, though: Kubernetes isn't some magical device. Fundamentally, it is merely a system for safely running and managing your apps in containers, whether you have one app or a thousand.
Consider Kubernetes to be the babysitter for your app. It manages how apps locate and communicate with one another, keeps things running, and restarts things when they malfunction. No-fluff features:
Scalability: One instance or 100 across machines can be run with ease.
Self-healing: Kubernetes automatically revives your app if it crashes.
Networking: Makes app-to-app communication easier.
Portability: It can operate on any platform, including Google Cloud, AWS, and your laptop.
Declarative Infrastructure: You specify your needs (in YAML), and Kubernetes determines how to fulfil them.

(Image Credits: ByteByteGo)
Pod: Smallest deployable unit. Think: 1 running container, or sometimes a small group sharing resources.
Deployment: Defines how many pods you want, what image to run, and keeps them alive.
Example from our YAML:
replicas: 2
Kubernetes will guarantee precisely two pod runs. if it passes away? automatically changed. There is a comprehensive YAML file in the following section, so don't worry.
Example:
name: redis-service
This allows your backend to always find Redis, even if the Redis pod changes under the hood.
Nana’s one-shot video gave me a clear understanding of how Kubernetes works under the hood. I referred to it and got a clear picture of every aspect of Kubernetes that I should know as a backend developer. I’d love for you to hold on to this blog, check out the video, and then come back later. :)
Note from me (Backend Dev to Backend Dev):
These aren’t just terms. They’re how you control your infra with YAML, not manual commands.
Once you understand Pods, Deployments, and Services, you understand 80% of Kubernetes workflows.
Why this matters:
Your Mac’s Docker and Minikube’s Docker are separate. Kubernetes doesn’t care about your host machine’s images unless you explicitly build inside Minikube’s Docker.
eval $(minikube docker-env) # Do this!
Now you are building inside Minikube.
# You will get the contxt for this command, hold on!
# Run this inside /backend folder.
docker build -t backend:latest .
So this is the project structure:
.
├── backend/ # Flask Application.
│ ├── app.py # Entry Point for Flask Application.
│ ├── requirements.txt # Python Dependencies.
│ └── Dockerfile # Dockerfile to Containerize Flask Application.
└── kubernetes/
├── backend-deployment.yaml # Deployment for Flask Application.
├── backend-service.yaml # Service to Expose Flask Application.
├── redis-deployment.yaml # Deployment for Redis DB.
└── redis-service.yaml # Service to Expose Redis DB.
If you need the code, it is here:
A basic Flask app talking to Redis. Nothing fancy, just enough to understand Kubernetes.
from flask import Flask, request
import redis
import os
app = Flask(__name__)
redis_host = os.getenv('REDIS_HOST', 'localhost')
redis_port = int(os.getenv('REDIS_PORT', 6379))
r = redis.Redis(host=redis_host, port=redis_port, decode_responses=True)
@app.route('/set', methods=['POST'])
def set_value():
key = request.json.get('key')
value = request.json.get('value')
r.set(key, value)
return {'status': 'ok'}
@app.route('/get/<key>')
def get_value(key):
value = r.get(key)
return {'key': key, 'value': value}
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000)
and a Dockerfile,
# Use the official Python 3.11 slim image as the base.
FROM python:3.11-slim
# Set the working directory inside the container to /app.
WORKDIR /app
# Copy all files from the current directory to /app in the container.
COPY . .
# Install Python dependencies from requirements.txt.
RUN pip install -r requirements.txt
# Define the default command to run the Flask app.
CMD ["python", "app.py"]
Yes, YAML looks scary at first. I used to feel the same.
But once you read it like "what is this telling Kubernetes to do for me?", it becomes super logical.
Let me break it down line-by-line for both Redis and Backend.
apiVersion: apps/v1
Inform Kubernetes of the API version you use for deployments.
The standard for deployments is apps/v1. Kubernetes won't even understand what you're attempting to explain without this.
kind: Deployment
This tells Kubernetes: "I want you to manage pods for me."
A Deployment keeps pods running, replaces them if they crash, and scales them up or down.
metadata:
name: redis
Human-friendly name for this deployment.
Later when I run kubectl get deployments, this name shows up.
spec:
replicas: 1
How many copies (pods) do I want?
Here, just 1. For learning, that’s enough. If Redis crashes, Kubernetes will recreate it.
selector:
matchLabels:
app: redis
The question, "Which pods does this deployment manage?" must be answered by Kubernetes.
The link is labels. For this deployment to manage pods, they must match this label.
template:
metadata:
labels:
app: redis
These labels are what actually get stamped onto the pods.
The selector above looks for this. (IMPORTANT)
spec:
containers:
- name: redis
image: redis:7
This runs the official Redis Docker image.
No magic, no AI, no abstraction. This is literally pulling Redis from Docker Hub and running it.
ports:
- containerPort: 6379
Tells Kubernetes this container listens on 6379 inside the pod.
Later, Services will use this info to direct traffic properly.
apiVersion: v1
Standard API version for Services.
kind: Service
This is saying: “Expose this pod internally via stable networking.”
Otherwise, pods restart and change names/IPs constantly.
metadata:
name: redis-service
This becomes the internal DNS name: Your backend will call redis-service:6379.
spec:
selector:
app: redis
Connect this service to pods labeled app: redis.
That’s how Kubernetes knows which pod to route traffic to.
ports:
- protocol: TCP
port: 6379
targetPort: 6379
Exposes 6379 inside the cluster.
Traffic on this Service’s port 6379 goes to the Redis pod’s 6379.
type: ClusterIP
Internal-only networking.
No exposure to the outside world. Only pods inside Kubernetes can reach this Redis.
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
Same logic as Redis. Just this time it’s our Python application. :)
spec:
replicas: 1
One instance for now. In production, this might be 3+ for resilience.
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
Same as Redis: Deployment looks for pods with app: backend.
spec:
containers:
- name: backend
image: backend:latest # The command you ran in the previous section inside backend folder.
imagePullPolicy: Never
This runs our Docker image built locally in Minikube.imagePullPolicy: Never forces Kubernetes to use our local image, not look for it on Docker Hub.
ports:
- containerPort: 5000
Our Flask app listens on 5000 inside the pod.
env:
- name: REDIS_HOST
value: redis-service
- name: REDIS_PORT
value: "6379"
Environment variables inside the container.
Our Python code uses REDIS_HOST to find Redis through Kubernetes DNS: redis-service:6379.
apiVersion: v1
kind: Service
metadata:
name: backend-service
Service name we will port-forward to.
spec:
selector:
app: backend
Routes traffic to pods labeled app: backend.
ports:
- protocol: TCP
port: 80
targetPort: 5000
Exposes 80 inside the cluster, forwards traffic to 5000 on the pod.
Why? Because most HTTP tools assume port 80 unless told otherwise.
type: ClusterIP
Again, internal-only networking in the cluster.
But I want to test this locally. So I run:
kubectl port-forward svc/backend-service 8000:80
Now localhost:8000 connects to Kubernetes Service → Pod.
In the project root directory, run the following commands:
kubectl apply -f kubernetes/redis-deployment.yaml
kubectl apply -f kubernetes/redis-service.yaml
kubectl apply -f kubernetes/backend-deployment.yaml
kubectl apply -f kubernetes/backend-service.yaml

This simple flow explains exactly how traffic moves inside Kubernetes when you run this project. When you access localhost:8000, Kubernetes uses port-forwarding to route your request to the backend-service on port 80. That service knows how to find the backend-pod, where your Python Flask app is running on port 5000. When your Flask app needs to access Redis, it doesn't use IPs — it simply calls redis-service:6379, and Kubernetes handles the networking behind the scenes. The redis-service then forwards traffic to the redis-pod running the Redis server.
This is how Kubernetes DNS and services keep everything connected cleanly.
Congratulations if you've made it this far. Most backend developers keep putting off doing this because Kubernetes seems "too big," "too enterprise," or "too confusing."
While developing this small Python + Redis project on Kubernetes, I came to the straightforward conclusion that: Kubernetes is not difficult. It's merely methodical.
There is a purpose behind each YAML line. Each deployment, pod, and service fits together like a puzzle.
You cease to be afraid of the larger picture once you comprehend these little details.
Kubernetes is no longer seen as "DevOps magic," but rather as a potent instrument that allows you to manage the infrastructure of your applications.
And truthfully?
That is the most fulfilling feeling a backend developer can have.
You are aware of what is operating, how it operates, and why it functions.
If you ever need help or just want to chat, DM me on Twitter / X or LinkedIn.