Cloud Architecture

Kubernetes for Developers: Deploy, Scale, Manage Containers

Master Kubernetes for developers. Learn to deploy, scale, and manage containerized applications with code examples, architecture, and real-world scenarios.

Khader Vali August 11, 2026 17 min read

As a senior engineer and technical writer, I’ve seen firsthand how the landscape of application deployment has transformed. Gone are the days of deploying monolithic applications onto bare metal servers, or even single virtual machines, without a robust orchestration layer. Today, microservices architectures and containerization have become the de facto standard for building scalable, resilient, and agile applications. But with the power of containers comes the complexity of managing hundreds, if not thousands, of them across a distributed system. This is where Kubernetes steps in, revolutionizing how developers deploy, scale, and manage their applications.

At Khadervali.com, our goal is to empower developers with the knowledge and tools they need to navigate this exciting, yet sometimes daunting, world. In this comprehensive article, we’ll dive deep into Kubernetes from a developer’s perspective. We’ll explore its core concepts, understand its architecture, walk through practical deployment and scaling strategies, and discuss best practices for managing your containerized applications effectively. Whether you’re new to Kubernetes or looking to deepen your understanding, prepare to level up your container orchestration skills.

The Evolution of Application Deployment: Why Kubernetes Became Essential

Before we jump into the “how,” let’s briefly touch upon the “why.” Understanding the problem Kubernetes solves provides crucial context and appreciation for its design.

From Monoliths to Microservices: A Shifting Paradigm

For decades, the dominant architectural style was the monolith – a single, tightly coupled application encompassing all business logic. While simpler to develop initially, monoliths often suffered from:

  • Slow Development Cycles: A small change in one part required recompiling and redeploying the entire application.
  • Scalability Challenges: You had to scale the entire application even if only a small component was bottlenecked.
  • Technology Lock-in: Difficult to adopt new technologies without a massive refactor.
  • Reliability Issues: A bug in one component could bring down the entire application.

Microservices emerged as a response, advocating for breaking down applications into small, independent, loosely coupled services, each running in its own process and communicating via lightweight mechanisms (like APIs). This approach offers:

  • Independent Development: Teams can work on services independently.
  • Scalability: Individual services can be scaled based on demand.
  • Technology Diversity: Different services can use different tech stacks.
  • Resilience: Failure in one service is less likely to affect others.

The Rise of Containers: Packaging for Portability

With microservices came the need for consistent and isolated environments. Virtual Machines (VMs) provided isolation but were heavy and slow to start. Containers, popularized by Docker, offered a lightweight alternative:

  • Isolation: Each container runs in an isolated environment, preventing conflicts.
  • Portability: “Build once, run anywhere” – a container image runs consistently across different environments (dev, test, prod).
  • Efficiency: Less overhead than VMs, faster startup times.

Containers solved the “it works on my machine” problem, but they introduced a new challenge: managing hundreds or thousands of containers across a cluster of machines. How do you deploy them? How do you ensure they stay running? How do you scale them up or down? How do you make them discoverable?

The Orchestration Imperative: Enter Kubernetes

This is where container orchestration platforms became indispensable. Early solutions included Docker Swarm and Apache Mesos. However, Kubernetes, originally developed by Google (and open-sourced in 2014), quickly emerged as the dominant force. Its robust feature set, extensible architecture, and vibrant community cemented its place as the standard for managing containerized applications at scale.

Kubernetes essentially takes care of the operational burden of running your containers, allowing developers to focus on writing code and building features, rather than worrying about the underlying infrastructure.

Kubernetes Fundamentals: A Developer’s Perspective

At its core, Kubernetes is an open-source system for automating deployment, scaling, and management of containerized applications. It groups containers that make up an application into logical units for easy management and discovery.

Understanding the Kubernetes Architecture (Diagram in Words)

Imagine a Kubernetes cluster as a highly organized city for your applications. It consists of two main types of machines (nodes):

1. The Control Plane (The City Hall / Brain)

These nodes manage the entire cluster. They make global decisions about the cluster (e.g., scheduling), detect and respond to cluster events (e.g., starting a new pod), and manage the cluster’s state. You typically interact with the control plane indirectly via the kubectl command-line tool.

  • kube-apiserver: The front end for the Kubernetes control plane. It exposes the Kubernetes API, allowing communication with the cluster. All internal and external requests go through this component. Think of it as the main entrance to City Hall.
  • etcd: A highly available key-value store that serves as Kubernetes’ backing store for all cluster data. It’s the ultimate source of truth for the cluster’s state. This is the central database where all city records are kept.
  • kube-scheduler: Watches for newly created Pods with no assigned node and selects a node for them to run on. It considers resource requirements, hardware/software/policy constraints, affinity and anti-affinity specifications, and data locality. This is the city planner, deciding where new buildings (Pods) should be constructed.
  • kube-controller-manager: Runs various controller processes. Each controller watches for a specific state and attempts to move the current state towards the desired state. Examples include:
    • Node Controller: Responsible for noticing and responding when nodes go down.
    • Replication Controller: Maintains the correct number of pods for every replication controller object.
    • Endpoints Controller: Populates the Endpoints object (which joins Services & Pods).
    • Service Account & Token Controllers: Create default accounts and API access tokens for new Namespaces.

    This is the city’s operations department, ensuring everything is running as planned.

  • cloud-controller-manager (Optional): If your Kubernetes cluster is running on a cloud provider (AWS, GCP, Azure), this component links the cluster to the cloud provider’s API to manage resources like load balancers, persistent volumes, and network routes.

2. Worker Nodes (The Neighborhoods / Workhorses)

These are the machines where your actual applications (containers) run. They receive instructions from the control plane and execute them.

  • kubelet: An agent that runs on each node in the cluster. It ensures that containers are running in a Pod. The kubelet takes a set of PodSpecs that are provided through various mechanisms and ensures that the containers described in those PodSpecs are running and healthy. This is the site manager for each neighborhood, making sure buildings are constructed and maintained according to plan.
  • kube-proxy: A network proxy that runs on each node. It maintains network rules on nodes, allowing network communication to your Pods from inside or outside the cluster. It handles service discovery and load balancing for your applications. This is the traffic controller, directing vehicles (network requests) to the correct buildings (Pods).
  • Container Runtime: The software responsible for running containers. Docker used to be the default, but Kubernetes now supports other Container Runtime Interface (CRI) compatible runtimes like containerd and CRI-O. This is the construction crew that actually builds and runs the containers.
Kubernetes for Developers: Deploy, Scale, Manage Containers
Generated Image

Key Kubernetes Objects for Developers

As a developer, you primarily interact with Kubernetes through its API objects. These objects describe the desired state of your applications and infrastructure. You declare what you want, and Kubernetes works to make it so.

1. Pods: The Smallest Deployable Unit

A Pod is the smallest, most basic deployable unit in Kubernetes. It represents a single instance of a running process in your cluster. A Pod typically contains one (the most common scenario) or more tightly coupled containers that share resources like network, storage, and IPC namespace. If you have multiple containers in a Pod, they should be co-located and share a common lifecycle (e.g., an application container and a “sidecar” helper container like a log shipper or proxy).

Key characteristics:

  • Ephemeral: Pods are designed to be short-lived. If a Pod dies, Kubernetes replaces it with a new one.
  • Unique IP: Each Pod gets its own unique IP address within the cluster.
  • Shared resources: Containers within a Pod share the same network namespace (IP address, port space) and can share storage volumes.

2. Deployments: Managing Your Application’s Lifecycle

While you *can* create Pods directly, you rarely do. Instead, you use Deployments. A Deployment manages a set of identical Pods, ensuring that a specified number of Pods are always running. It provides declarative updates for Pods and ReplicaSets (which Deployments manage). Deployments are your go-to for deploying stateless applications.

Key characteristics:

  • Declarative Updates: You describe the desired state (e.g., “I want 3 replicas of my Nginx app with image 1.19.0”), and Kubernetes handles the transition.
  • Rolling Updates: Allows for zero-downtime updates by gradually replacing old Pods with new ones.
  • Rollbacks: Easily revert to a previous application version if something goes wrong.
  • Self-healing: If a Pod crashes, the Deployment ensures a new one is created.

3. ReplicaSets: Ensuring Desired Pod Count (Usually Managed by Deployments)

A ReplicaSet’s purpose is to maintain a stable set of replica Pods running at any given time. It’s the workhorse behind Deployments, ensuring that if Pods fail, new ones are created, and if too many exist, extras are terminated. As a developer, you’ll rarely interact directly with ReplicaSets; Deployments manage them for you.

4. Services: Stable Network Access to Pods

Pods are ephemeral and have dynamic IP addresses. If a Pod dies and a new one is created, its IP address changes. How do other applications find and communicate with it? Services solve this problem. A Service is an abstract way to expose an application running on a set of Pods as a network service. It provides a stable IP address and DNS name for a set of Pods, acting as a load balancer among them.

Service Types:

  • ClusterIP: Exposes the Service on an internal IP in the cluster. This type is only reachable from within the cluster. Ideal for internal microservice communication.
  • NodePort: Exposes the Service on each Node’s IP at a static port (the NodePort). A ClusterIP Service is automatically created. You can access the NodePort Service from outside the cluster by requesting `:`.
  • LoadBalancer: Exposes the Service externally using a cloud provider’s load balancer. This will provision an external IP address that acts as the entry point to your service.
  • ExternalName: Maps the Service to the contents of the externalName field (e.g., a DNS name), by returning a CNAME record. No proxying is involved.

5. Ingress: External Access with HTTP/S Routing

For exposing HTTP/S applications to the outside world, especially when you need advanced routing rules, SSL termination, or virtual hosting, you use Ingress. An Ingress resource manages external access to the services in a cluster, typically HTTP. It provides load balancing, SSL termination, and name-based virtual hosting. An Ingress controller (e.g., Nginx Ingress Controller, Traefik) must be running in the cluster to fulfill the Ingress rules.

6. ConfigMaps and Secrets: Configuration and Sensitive Data

Applications often need configuration data (e.g., database connection strings, API keys, feature flags). Kubernetes provides two mechanisms:

  • ConfigMaps: Used to store non-confidential configuration data as key-value pairs. They can be consumed by Pods as environment variables, command-line arguments, or as files in a volume.
  • Secrets: Similar to ConfigMaps but designed for sensitive information like passwords, OAuth tokens, and SSH keys. Secrets are base64 encoded by default (not encrypted!), so additional security measures (like integrating with external secret management systems) are often recommended for production.

7. Persistent Volumes (PV) & Persistent Volume Claims (PVC): Storage Management

Containers are ephemeral; any data written inside a container is lost when the container is terminated. For stateful applications (databases, message queues), you need persistent storage. Kubernetes abstracts this with:

  • Persistent Volume (PV): A piece of storage in the cluster that has been provisioned by an administrator or dynamically provisioned using StorageClasses. It’s a cluster resource, independent of Pods.
  • Persistent Volume Claim (PVC): A request for storage by a user (developer). A Pod requests a PVC, which then binds to an available PV. This decouples storage consumption from storage provision.

8. Namespaces: Resource Isolation

As clusters grow, so does the number of teams and applications. Namespaces provide a mechanism for isolating groups of resources within a single Kubernetes cluster. They’re ideal for organizing resources for different teams, environments (dev, test, prod), or projects. Resources within a Namespace must have unique names, but resources in different Namespaces can have the same name.

Deploying Applications to Kubernetes: Your First Steps

Now that we understand the core components, let’s get practical. How do you actually get your application running on Kubernetes?

Local Development with Minikube or Kind

Before deploying to a shared cluster, it’s often useful to run a local Kubernetes cluster on your machine for development and testing. Popular tools include:

  • Minikube: Runs a single-node Kubernetes cluster inside a VM on your laptop.
  • Kind (Kubernetes in Docker): Runs local Kubernetes clusters using Docker containers as “nodes.” Great for CI/CD pipelines.

For Minikube, simply install it and run:

minikube start

This will start a local cluster, and configure your kubectl to interact with it.

YAML Manifests: The Language of Kubernetes

Kubernetes objects are defined using YAML (or JSON) files, often called “manifests.” These files declaratively describe the desired state of your resources.

Example: Deploying a Simple Nginx Web Server

Let’s create a Deployment for Nginx:

# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3 # We want 3 instances of Nginx
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.21.6 # Using a specific Nginx image
        ports:
        - containerPort: 80
        resources: # Define resource requests and limits
          requests:
            memory: "64Mi"
            cpu: "250m" # 250 milli-cores = 0.25 CPU core
          limits:
            memory: "128Mi"
            cpu: "500m"

And then expose it with a Service:

# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx # This selector matches the Pods created by our Deployment
  ports:
    - protocol: TCP
      port: 80       # Port the Service listens on
      targetPort: 80 # Port the container exposes
  type: ClusterIP   # Internal service, accessible only within the cluster

kubectl: Your Command-Line Interface to Kubernetes

The primary tool for interacting with your Kubernetes cluster is kubectl. It allows you to run commands against Kubernetes clusters, including deploying applications, inspecting and managing cluster resources, and viewing logs.

Applying Your Manifests

To deploy the Nginx application:

kubectl apply -f nginx-deployment.yaml
kubectl apply -f nginx-service.yaml

Checking Your Resources

You can verify that your Pods, Deployment, and Service are running:

kubectl get deployments
kubectl get pods
kubectl get services

You should see output similar to this:

# kubectl get deployments
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           2m

# kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-78f55755d7-2fglc   1/1     Running   0          2m
nginx-deployment-78f55755d7-4k2z6   1/1     Running   0          2m
nginx-deployment-78f55755d7-h8j9m   1/1     Running   0          2m

# kubectl get services
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
kubernetes      ClusterIP   10.96.0.1        <none>        443/TCP   10m
nginx-service   ClusterIP   10.106.123.145   <none>        80/TCP    1m

Accessing Logs and Executing Commands

For debugging, you’ll frequently need to check logs or execute commands inside a running container:

# Get logs from one of the Nginx pods
kubectl logs nginx-deployment-78f55755d7-2fglc

# Execute a shell inside a running Nginx pod
kubectl exec -it nginx-deployment-78f55755d7-2fglc -- /bin/bash

Real-World Scenario: Deploying a Multi-Tier Web Application

Let’s consider a common scenario: a simple web application with a frontend (e.g., React/Vue served by Nginx) and a backend API (e.g., Node.js/Python/Go).

1. Backend Deployment and Service (e.g., Node.js API)

# backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend-api-deployment
  labels:
    app: backend-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend-api
  template:
    metadata:
      labels:
        app: backend-api
    spec:
      containers:
      - name: backend-api
        image: khadervali/my-backend-api:v1.0.0 # Your custom backend image
        ports:
        - containerPort: 3000
        env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: app-secrets # Referencing a Secret
              key: db_connection_string
        resources:
          requests:
            memory: "128Mi"
            cpu: "500m"
          limits:
            memory: "256Mi"
            cpu: "1"
---
# backend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: backend-api-service
spec:
  selector:
    app: backend-api
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: ClusterIP # Internal service

2. Frontend Deployment and Service (e.g., React App)

# frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-app-deployment
  labels:
    app: frontend-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: frontend-app
  template:
    metadata:
      labels:
        app: frontend-app
    spec:
      containers:
      - name: frontend-app
        image: khadervali/my-frontend-app:v1.0.0 # Your custom frontend image
        ports:
        - containerPort: 80
        env:
        - name: REACT_APP_API_URL
          value: "http://backend-api-service" # Internal service name for backend
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"
---
# frontend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: frontend-app-service
spec:
  selector:
    app: frontend-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer # Expose externally if on a cloud provider
                     # Or NodePort if you're using Minikube/Kind and want to test externally

3. Exposing the Frontend with Ingress (Optional, but Recommended for Production)

If you’re running on a cluster with an Ingress controller, you’d define an Ingress:

# frontend-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: frontend-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: myapp.khadervali.com # Your custom domain
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-app-service
            port:
              number: 80
  # You can add TLS here as well, referencing a Secret for the certificate
  # tls:
  # - hosts:
  #   - myapp.khadervali.com
  #   secretName: myapp-tls-secret

This setup uses ClusterIP for internal backend-API communication and LoadBalancer (or Ingress) for external frontend access. Notice how the frontend uses the internal DNS name backend-api-service to reach the backend, thanks to Kubernetes’ built-in DNS service discovery.

Scaling Applications in Kubernetes

One of Kubernetes’ most powerful features is its ability to scale applications effortlessly, both manually and automatically.

Manual Scaling

You can manually adjust the number of Pod replicas for a Deployment at any time:

kubectl scale deployment/frontend-app-deployment --replicas=5

Kubernetes will then create or terminate Pods to match the desired replica count, ensuring your application can handle increased or decreased load.

Horizontal Pod Autoscaler (HPA): Automated Scaling

Manual scaling is fine for predictable loads, but for dynamic traffic, the Horizontal Pod Autoscaler (HPA) is invaluable. HPA automatically scales the number of Pod replicas in a Deployment (or ReplicaSet, StatefulSet) based on observed CPU utilization, memory utilization, or custom metrics.

Here’s how to set up an HPA for our backend API:

# backend-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: backend-api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: backend-api-deployment
  minReplicas: 2 # Minimum number of pods
  maxReplicas: 10 # Maximum number of pods
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70 # Target 70% average CPU utilization

Apply this with kubectl apply -f backend-hpa.yaml. Now, if the average CPU utilization of the backend-api-deployment Pods exceeds 70%, Kubernetes will add more Pods (up to 10). If it drops below, it will scale down (to a minimum of 2).

You can check the HPA status with:

kubectl get hpa
Kubernetes for Developers: Deploy, Scale, Manage Containers
Generated Image

Vertical Pod Autoscaler (VPA) and Cluster Autoscaler (Brief Mention)

  • Vertical Pod Autoscaler (VPA): (Less common for developers to directly configure, but good to know) VPA automatically adjusts the CPU and memory requests and limits for containers in your Pods. This is useful for “right-sizing” your workloads to optimize resource usage and cost.
  • Cluster Autoscaler: This operates at the cluster level, not the Pod level. It automatically adjusts the number of nodes in your Kubernetes cluster. If there are pending Pods that cannot be scheduled due to insufficient resources, the Cluster Autoscaler adds more nodes. If nodes are underutilized for a period, it removes them.

Rolling Updates and Rollbacks: Smooth Deployments

Deployments handle updates gracefully using a “rolling update” strategy. When you update a Deployment (e.g., change the image version), Kubernetes:

  1. Creates new Pods with the updated configuration.
  2. Gradually brings up the new Pods while simultaneously taking down old Pods.
  3. Ensures that a minimum number of Pods are always available during the update.

This ensures zero-downtime deployments. If something goes wrong with the new version, you can easily roll back:

#

Written by

Khader Vali

Senior Software Engineer specializing in cloud architecture, real-time systems, and enterprise-scale applications.

Share this article

Related Articles

Mastering Infrastructure Testing: Terratest & Kitchen-Terraform

Jul 08, 2026 · 13 min read

Implementing LLM Integration Patterns in Production Systems

Sep 02, 2024 · 1 min read

Infra Testing: Terratest & Kitchen for Terraform Modules

Jul 29, 2026 · 15 min read