DevToolBoxGRATIS
Blog

Kubernetes Complete Guide for Developers: Pods, Helm, RBAC, and CI/CD

13 min readby DevToolBox
TL;DR

Kubernetes (K8s) is the industry-standard container orchestration platform. Key concepts: Pods (smallest deployable unit), Deployments (manage replicas + rolling updates), Services (stable networking), ConfigMaps/Secrets (configuration), Ingress (HTTP routing), PersistentVolumes (durable storage), and HPA (autoscaling). Use kubectl to interact with the cluster and Helm to package applications as charts.

Key Takeaways
  • Kubernetes automates deployment, scaling, and self-healing of containerized applications across clusters.
  • Pods are the smallest unit; Deployments manage Pods at scale with rolling updates and rollback support.
  • Services provide stable DNS names and load balancing; Ingress routes HTTP traffic to multiple services.
  • HPA autoscales Pod replicas based on CPU/memory; the Cluster Autoscaler adds/removes nodes automatically.
  • Helm packages applications as Charts — reusable, versioned, configurable — and manages release lifecycle.
  • PersistentVolumes and StorageClasses provide durable storage for stateful workloads like databases.
  • For production, use a managed Kubernetes service (EKS, GKE, AKS) to eliminate control plane operations.

What Is Kubernetes and Why It Matters

Kubernetes (K8s) is an open-source container orchestration system originally designed by Google and donated to the Cloud Native Computing Foundation (CNCF) in 2014. It automates the deployment, scaling, and lifecycle management of containerized applications across clusters of machines. In 2026, Kubernetes is the de facto standard for running production workloads at scale — from startups shipping their first microservice to hyperscalers managing millions of containers per day.

Before Kubernetes, teams managed servers manually: custom deployment scripts, fragile SSH-based deploys, and hand-rolled load balancers. Docker solved the "it works on my machine" problem by packaging apps into containers, but running thousands of containers across dozens of servers without orchestration is chaos. Kubernetes brings order: you describe the desired state of your system in YAML manifests, and Kubernetes continuously reconciles the actual state to match. A container crashes? Kubernetes restarts it. A node dies? Kubernetes reschedules the workloads to healthy nodes. Traffic spikes? Kubernetes scales out. Traffic drops? Kubernetes scales back in — automatically.

The platform provides self-healing, horizontal scaling, rolling updates with zero downtime, secret management, service discovery, load balancing, and storage orchestration — all as built-in primitives. This guide covers everything you need to go from "what is a Pod?" to running a production-grade application with Helm, autoscaling, and persistent storage.

Key Kubernetes Features

  • Self-healing: automatically restarts failed containers, replaces unresponsive Pods, kills containers that fail health checks, and reschedules them on healthy nodes.
  • Horizontal scaling: scale applications up or down with a single command (kubectl scale) or automatically based on CPU, memory, or custom metrics via HPA.
  • Rolling updates & rollbacks: deploy new versions with zero downtime using configurable rolling update strategies; roll back instantly if anything goes wrong.
  • Service discovery & load balancing: built-in DNS and kube-proxy distribute traffic automatically across healthy Pod instances without manual configuration.
  • Configuration management: separate app code from configuration using ConfigMaps (non-sensitive) and Secrets (sensitive), injected as environment variables or file mounts.
  • Storage orchestration: automatically provision and attach cloud or on-premises storage to Pods using PersistentVolumes and StorageClasses.
  • Infrastructure abstraction: the same Kubernetes manifests run on AWS EKS, Google GKE, Azure AKS, or bare-metal with minimal modifications.
  • Extensibility: CustomResourceDefinitions (CRDs) and Operators allow you to extend Kubernetes with domain-specific automation for databases, messaging systems, and more.
Kubernetes Cluster Architecture

┌─────────────────────────── CONTROL PLANE ───────────────────────────┐
│                                                                      │
│  ┌──────────────┐  ┌──────────────┐  ┌────────────────────────┐    │
│  │  API Server  │  │  Scheduler   │  │  Controller Manager    │    │
│  │  (REST API)  │  │  (pod→node)  │  │  (node/replica/svc)    │    │
│  └──────┬───────┘  └──────────────┘  └────────────────────────┘    │
│         │                                                            │
│  ┌──────┴───────┐  ┌──────────────┐                                │
│  │     etcd     │  │    Cloud     │                                │
│  │  (key-value) │  │  Controller  │                                │
│  └──────────────┘  └──────────────┘                                │
└──────────────────────────────────────────────────────────────────────┘
         │                     │                     │
         ▼                     ▼                     ▼
┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
│  Worker Node 1   │  │  Worker Node 2   │  │  Worker Node 3   │
│                  │  │                  │  │                  │
│  ┌────────────┐  │  │  ┌────────────┐  │  │  ┌────────────┐  │
│  │  kubelet   │  │  │  │  kubelet   │  │  │  │  kubelet   │  │
│  └────────────┘  │  │  └────────────┘  │  │  └────────────┘  │
│  ┌────────────┐  │  │  ┌────────────┐  │  │  ┌────────────┐  │
│  │ kube-proxy │  │  │  │ kube-proxy │  │  │  │ kube-proxy │  │
│  └────────────┘  │  │  └────────────┘  │  │  └────────────┘  │
│  ┌────────────┐  │  │  ┌────────────┐  │  │  ┌────────────┐  │
│  │ containerd │  │  │  │ containerd │  │  │  │ containerd │  │
│  └────────────┘  │  │  └────────────┘  │  │  └────────────┘  │
│  [Pod][Pod][Pod] │  │  [Pod][Pod][Pod] │  │  [Pod][Pod][Pod] │
└──────────────────┘  └──────────────────┘  └──────────────────┘

Core Concepts: Pods, Deployments, Services, and More

Pods — The Smallest Deployable Unit

A Pod is the atomic unit of Kubernetes — the smallest thing you can deploy. A Pod wraps one or more containers that share the same network namespace (they communicate via localhost) and storage volumes. In practice, most Pods run a single application container, with optional sidecar containers for logging, proxying (Envoy/Istio), or secrets injection (Vault Agent).

Deployments — Managing Replicas and Updates

A Deployment is a higher-level abstraction that manages a ReplicaSet, which in turn manages a set of identical Pods. You tell the Deployment "I want 3 replicas of this container image" and it creates, monitors, and maintains exactly 3 running Pods. When you update the image, the Deployment orchestrates a rolling update — spinning up new Pods before terminating old ones — ensuring zero downtime.

Services — Stable Networking Endpoints

Pods are ephemeral — they can be created, destroyed, and rescheduled at any time with different IP addresses. A Service provides a stable virtual IP (ClusterIP) and DNS name that proxies traffic to a set of Pods selected by labels. Four Service types exist: ClusterIP (internal cluster access), NodePort (accessible on each node IP), LoadBalancer (cloud provider external load balancer), and ExternalName (DNS alias).

ReplicaSets — Ensuring Availability

A ReplicaSet ensures that a specified number of Pod replicas are running at any given time. If a Pod crashes, the ReplicaSet controller immediately creates a replacement. ReplicaSets are rarely created directly; Deployments manage them automatically and provide the additional capability of rolling updates and rollback history.

Namespaces — Logical Isolation

Namespaces partition a single cluster into multiple virtual clusters. They are the primary mechanism for multi-tenancy: separating dev, staging, and production environments; isolating teams; or organizing microservices. Resources within a namespace share the same DNS domain and can be subject to ResourceQuotas and LimitRanges to prevent any one namespace from consuming all cluster resources.

kubectl Commands: The Complete Reference

kubectl is the command-line tool for interacting with a Kubernetes cluster. It communicates with the Kubernetes API server and is your primary interface for deploying applications, inspecting resources, debugging problems, and managing the cluster.

Install kubectl and Set Up a Local Cluster

# Install kubectl (macOS)
brew install kubectl

# Install kubectl (Linux)
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install kubectl /usr/local/bin/kubectl

# Verify installation
kubectl version --client

# ── Local Cluster Options ─────────────────────────────────────────

# Option 1: minikube (recommended for beginners)
brew install minikube
minikube start --cpus=4 --memory=8192 --driver=docker
minikube dashboard          # Open web UI
minikube stop

# Option 2: kind (Kubernetes in Docker)
brew install kind
kind create cluster --name dev
kind get clusters
kind delete cluster --name dev

# Option 3: Docker Desktop
# Settings → Kubernetes → Enable Kubernetes → Apply & Restart

# Check cluster connection
kubectl cluster-info
kubectl get nodes -o wide

Essential kubectl Commands

# ── Cluster & Nodes ──────────────────────────────────────────────
kubectl cluster-info                      # Show control plane URL
kubectl get nodes                         # List all nodes
kubectl get nodes -o wide                 # Nodes with IP, OS, runtime
kubectl top nodes                         # CPU & memory usage per node
kubectl describe node <node-name>         # Detailed node info & events

# ── Pods ─────────────────────────────────────────────────────────
kubectl get pods                          # Pods in current namespace
kubectl get pods -A                       # Pods across all namespaces
kubectl get pods -n staging               # Pods in staging namespace
kubectl get pods -o wide                  # Pods with node & IP
kubectl get pods --show-labels            # Show pod labels
kubectl get pods -l app=web-app           # Filter by label
kubectl get pods -w                       # Watch pod status live

kubectl describe pod <pod-name>           # Detailed pod info & events
kubectl logs <pod-name>                   # Container stdout logs
kubectl logs <pod-name> -f                # Stream logs (follow)
kubectl logs <pod-name> --previous        # Logs from crashed container
kubectl logs <pod-name> -c <container>    # Logs from specific container

kubectl exec -it <pod-name> -- bash       # Shell into pod
kubectl exec -it <pod-name> -- /bin/sh    # Shell (Alpine/BusyBox)
kubectl exec <pod-name> -- env            # Print environment variables

kubectl port-forward pod/<pod-name> 8080:80   # Local port 8080 to pod port 80
kubectl port-forward svc/<svc-name> 8080:80   # Local port to service port

kubectl delete pod <pod-name>             # Delete pod (Deployment recreates it)

# ── Deployments ──────────────────────────────────────────────────
kubectl get deployments
kubectl get deploy -o wide
kubectl describe deployment <name>

kubectl apply -f deployment.yaml          # Create or update
kubectl delete -f deployment.yaml         # Delete
kubectl diff -f deployment.yaml           # Preview changes

kubectl scale deploy <name> --replicas=5  # Scale manually
kubectl set image deploy/<name> app=myimage:2.0.0  # Update image

kubectl rollout status deploy/<name>      # Watch rollout progress
kubectl rollout history deploy/<name>     # View revision history
kubectl rollout undo deploy/<name>        # Rollback to previous
kubectl rollout undo deploy/<name> --to-revision=3  # Specific revision

# ── Services & Ingress ───────────────────────────────────────────
kubectl get services
kubectl get svc -o wide
kubectl describe svc <name>
kubectl get ingress
kubectl describe ingress <name>

# ── ConfigMaps & Secrets ─────────────────────────────────────────
kubectl get configmaps
kubectl describe configmap <name>
kubectl get secrets
kubectl get secret <name> -o jsonpath="{.data.password}" | base64 --decode

# ── Namespaces ───────────────────────────────────────────────────
kubectl get namespaces
kubectl create namespace staging
kubectl config set-context --current --namespace=staging

# ── Debugging ────────────────────────────────────────────────────
kubectl get events --sort-by=.metadata.creationTimestamp
kubectl get pod <name> -o yaml            # Full YAML spec
kubectl run debug-pod --image=busybox:latest -it --rm -- sh
kubectl top pods                          # Resource usage

# ── All Resources ────────────────────────────────────────────────
kubectl get all                           # All resources in namespace
kubectl get all -n my-namespace
kubectl get all -A                        # All resources everywhere

YAML Manifests: Pod, Deployment, Service, ConfigMap, Secret

Kubernetes resources are defined as YAML manifests and applied to the cluster using kubectl apply -f. Every manifest has four top-level fields: apiVersion, kind, metadata, and spec. Here are production-ready examples for the most common resource types.

Pod Manifest

# pod.yaml — Production-ready Pod definition
apiVersion: v1
kind: Pod
metadata:
  name: my-app
  namespace: production
  labels:
    app: my-app
    version: v1.0.0
    tier: backend
spec:
  containers:
    - name: app
      image: nginx:1.27-alpine
      ports:
        - containerPort: 80
          protocol: TCP
      resources:
        requests:
          memory: "64Mi"
          cpu: "100m"
        limits:
          memory: "128Mi"
          cpu: "250m"
      livenessProbe:
        httpGet:
          path: /healthz
          port: 80
        initialDelaySeconds: 10
        periodSeconds: 15
        failureThreshold: 3
      readinessProbe:
        httpGet:
          path: /ready
          port: 80
        initialDelaySeconds: 5
        periodSeconds: 10
      startupProbe:
        httpGet:
          path: /healthz
          port: 80
        failureThreshold: 30
        periodSeconds: 5
      lifecycle:
        preStop:
          exec:
            command: ["/bin/sh", "-c", "sleep 5"]
  terminationGracePeriodSeconds: 30
  restartPolicy: Always

Deployment Manifest (Production-Ready)

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  namespace: production
  labels:
    app: web-app
  annotations:
    kubernetes.io/change-cause: "Release v1.2.0 — performance improvements"
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1          # Max 1 extra Pod during update
      maxUnavailable: 0    # No downtime
  template:
    metadata:
      labels:
        app: web-app
        version: "1.2.0"
    spec:
      containers:
        - name: web
          image: my-registry/web-app:1.2.0
          ports:
            - containerPort: 3000
          env:
            - name: NODE_ENV
              value: "production"
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: password
          envFrom:
            - configMapRef:
                name: app-config
          resources:
            requests:
              memory: "128Mi"
              cpu: "200m"
            limits:
              memory: "256Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 15
          readinessProbe:
            httpGet:
              path: /ready
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 5
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 10"]
      terminationGracePeriodSeconds: 60
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: web-app

Service Manifest — All Four Types

# ClusterIP — internal cluster access only (default)
apiVersion: v1
kind: Service
metadata:
  name: web-app-svc
  namespace: production
spec:
  type: ClusterIP
  selector:
    app: web-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000

---
# NodePort — accessible at <NodeIP>:30080 from outside cluster
apiVersion: v1
kind: Service
metadata:
  name: web-app-nodeport
spec:
  type: NodePort
  selector:
    app: web-app
  ports:
    - port: 80
      targetPort: 3000
      nodePort: 30080     # Must be 30000–32767

---
# LoadBalancer — provisions cloud LB (EKS/GKE/AKS)
apiVersion: v1
kind: Service
metadata:
  name: web-app-lb
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
spec:
  type: LoadBalancer
  selector:
    app: web-app
  ports:
    - port: 443
      targetPort: 3000

ConfigMap and Secret

# configmap.yaml — non-sensitive configuration
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: production
data:
  NODE_ENV: "production"
  LOG_LEVEL: "info"
  PORT: "3000"
  MAX_CONNECTIONS: "100"
  app.json: |
    {
      "features": {
        "darkMode": true,
        "betaFeatures": false
      },
      "pagination": {
        "defaultPageSize": 20
      }
    }

---
# secret.yaml — sensitive data (base64-encoded in etcd)
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: production
type: Opaque
stringData:
  username: admin
  password: "super-secret-password-change-me"
  url: "postgres://admin:password@postgres:5432/myapp"

---
# TLS Secret — for HTTPS certificates
apiVersion: v1
kind: Secret
metadata:
  name: tls-cert
  namespace: production
type: kubernetes.io/tls
data:
  tls.crt: <base64-encoded-cert>
  tls.key: <base64-encoded-key>

Helm: The Package Manager for Kubernetes

Helm is the package manager for Kubernetes, analogous to apt for Debian or npm for Node.js. A Helm chart is a collection of Kubernetes manifest templates with configurable values. Charts can be shared via repositories (Artifact Hub hosts thousands of community charts for databases, monitoring, ingress controllers, and more). Helm manages the release lifecycle: install, upgrade, rollback, and uninstall.

Helm v3 (current) eliminates the server-side Tiller component from Helm v2. Charts are versioned and releases can be inspected, diffed, and rolled back. For teams that need per-environment customization, Helm values files provide a clean separation between chart logic and deployment-specific configuration.

Helm Installation and Basic Commands

# Install Helm (macOS)
brew install helm

# Install Helm (Linux)
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# Verify
helm version

# ── Repository Management ────────────────────────────────────────
helm repo add stable https://charts.helm.sh/stable
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update                          # Refresh repo indices
helm repo list                            # Show configured repos

# ── Installing Charts ────────────────────────────────────────────
# Install NGINX Ingress Controller
helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace

# Install PostgreSQL with custom values
helm install my-postgres bitnami/postgresql \
  --namespace production \
  --set auth.postgresPassword=secret123 \
  --set primary.persistence.size=20Gi \
  --set primary.resources.requests.memory=256Mi

# Install with a values file (preferred for complex configs)
helm install my-release my-chart/ -f values-production.yaml

# ── Release Management ───────────────────────────────────────────
helm list                                 # List all releases
helm list -n production                   # Releases in namespace
helm status my-postgres                   # Release status
helm history my-postgres                  # Release history

# Upgrade a release
helm upgrade my-postgres bitnami/postgresql \
  --namespace production \
  --set auth.postgresPassword=secret123 \
  --set primary.persistence.size=30Gi

# Rollback to previous version
helm rollback my-postgres 1

# Uninstall a release
helm uninstall my-postgres -n production

# ── Chart Development ────────────────────────────────────────────
helm create my-app                        # Scaffold a new chart
helm lint my-app/                         # Validate chart syntax
helm template my-app my-app/ -f values.yaml  # Render templates locally
helm install my-app my-app/ --dry-run --debug
helm package my-app/                      # Package chart as .tgz

Helm Values File Example

# values-production.yaml
# Override default chart values for production environment

replicaCount: 3

image:
  repository: my-registry/web-app
  tag: "1.2.0"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: app.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: app-tls
      hosts:
        - app.example.com

resources:
  requests:
    memory: 256Mi
    cpu: 200m
  limits:
    memory: 512Mi
    cpu: 500m

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

config:
  NODE_ENV: production
  LOG_LEVEL: info

postgresql:
  enabled: true
  auth:
    database: myapp
    existingSecret: db-credentials
  primary:
    persistence:
      size: 20Gi
      storageClass: gp3

Kubernetes Networking: ClusterIP, NodePort, LoadBalancer, Ingress

Kubernetes networking operates at three levels: Pod-to-Pod communication (handled by the CNI plugin — Flannel, Calico, Cilium), Service-based load balancing (kube-proxy), and external access (Ingress controllers). Understanding each layer is critical for designing resilient, secure application architectures.

Ingress — HTTP Routing with TLS

# ingress.yaml — Route traffic to multiple services via one entry point
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  namespace: production
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - app.example.com
        - api.example.com
      secretName: app-tls-secret
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-svc
                port:
                  number: 80
    - host: api.example.com
      http:
        paths:
          - path: /v1
            pathType: Prefix
            backend:
              service:
                name: api-v1-svc
                port:
                  number: 80
          - path: /v2
            pathType: Prefix
            backend:
              service:
                name: api-v2-svc
                port:
                  number: 80

---
# ClusterIssuer — Let's Encrypt production certificates
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
      - http01:
          ingress:
            class: nginx

NetworkPolicy — Zero-Trust Pod Networking

# networkpolicy.yaml — Deny all, then allow only required paths
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-network-policy
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: api-server
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend        # Only frontend can call API
        - namespaceSelector:
            matchLabels:
              name: monitoring     # Prometheus can scrape metrics
      ports:
        - protocol: TCP
          port: 3000
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: postgres        # API can only reach the DB
      ports:
        - protocol: TCP
          port: 5432

Storage: PersistentVolumes, PersistentVolumeClaims, StorageClasses

Container storage is ephemeral by default — data is lost when a container restarts. For stateful applications (databases, caches, file storage), Kubernetes provides the PersistentVolume (PV) subsystem. A PV is a piece of storage in the cluster provisioned by an administrator or dynamically by a StorageClass. A PersistentVolumeClaim (PVC) is a request for storage by a user, specifying size and access mode. Kubernetes binds PVCs to matching PVs.

StorageClasses allow dynamic provisioning: instead of pre-creating PVs, the cluster automatically provisions storage (EBS on AWS, Persistent Disk on GCP, Azure Disk) when a PVC is created. This is the standard approach on managed Kubernetes. StatefulSets use volumeClaimTemplates to give each Pod instance its own stable, uniquely-named PVC that persists across Pod rescheduling.

StorageClass, PVC, and StatefulSet

# storageclass.yaml — Dynamic provisioning on AWS
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
  encrypted: "true"

---
# pvc.yaml — Request 20Gi of SSD storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data
  namespace: production
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 20Gi

---
# StatefulSet — PostgreSQL with per-pod stable storage
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: production
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16-alpine
          ports:
            - containerPort: 5432
          env:
            - name: POSTGRES_DB
              value: myapp
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: username
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-credentials
                  key: password
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
          volumeMounts:
            - name: postgres-storage
              mountPath: /var/lib/postgresql/data
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "1"
          livenessProbe:
            exec:
              command: ["pg_isready", "-U", "admin", "-d", "myapp"]
            initialDelaySeconds: 30
            periodSeconds: 10
  volumeClaimTemplates:
    - metadata:
        name: postgres-storage
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 20Gi

---
# Headless service for StatefulSet DNS
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: production
spec:
  clusterIP: None              # Headless — no virtual IP
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432

Auto-scaling: HPA, VPA, and Cluster Autoscaler

Kubernetes provides three complementary autoscaling mechanisms operating at different levels of the stack. The Horizontal Pod Autoscaler (HPA) scales the number of Pod replicas based on metrics. The Vertical Pod Autoscaler (VPA) adjusts resource requests and limits for existing Pods. The Cluster Autoscaler adjusts the number of nodes in the cluster based on pending Pods and node utilization.

For most applications, HPA is the primary autoscaling tool. It queries the metrics API (CPU, memory, or custom metrics from Prometheus Adapter) and scales the target Deployment or StatefulSet up or down with configurable stabilization windows to prevent thrashing.

Horizontal Pod Autoscaler (HPA)

# hpa.yaml — Scale web-app pods based on CPU and memory
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-app-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: "1000"
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Pods
          value: 4
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60

Vertical Pod Autoscaler (VPA)

# vpa.yaml — Automatically tune resource requests and limits
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web-app-vpa
  namespace: production
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  updatePolicy:
    updateMode: "Auto"
  resourcePolicy:
    containerPolicies:
      - containerName: web
        minAllowed:
          cpu: 100m
          memory: 128Mi
        maxAllowed:
          cpu: "2"
          memory: 2Gi
        controlledResources: ["cpu", "memory"]

Test Autoscaling with Load Generation

# Apply HPA
kubectl apply -f hpa.yaml

# Check current HPA status
kubectl get hpa -n production
kubectl describe hpa web-app-hpa -n production

# Generate CPU load to trigger autoscaling
kubectl run load-generator \
  --image=busybox:latest \
  --rm -it \
  --restart=Never \
  -- /bin/sh -c "while true; do wget -q -O- http://web-app-svc/; done"

# In a second terminal: watch pods scale up
kubectl get pods -n production -w

# Watch HPA metrics update live
kubectl get hpa -n production -w

# Stop load generator (Ctrl+C), watch pods scale down after 5 min
# Cluster Autoscaler adds nodes when pods are Pending
kubectl get nodes -w

Kubernetes vs Docker Swarm vs HashiCorp Nomad

Kubernetes is not the only container orchestrator. Docker Swarm is simpler and built into Docker Engine. HashiCorp Nomad is a general-purpose orchestrator that handles containers, binaries, Java JARs, and more. Here is how they compare across the dimensions that matter most in production:

FeatureKubernetesDocker SwarmHashiCorp Nomad
OriginGoogle → CNCF (2014)Docker Inc. (2016)HashiCorp (2015)
WorkloadsOCI containersDocker containers onlyContainers, binaries, JARs, VMs
Learning CurveHighLowMedium
Setup ComplexityHigh (many components)Very low (built-in to Docker)Low (single binary)
Auto-scalingHPA, VPA, Cluster AutoscalerBasic / manualDynamic task placement
Self-healingFull reschedule + restartBasic restart policiesJob re-scheduling
NetworkingCNI plugins, NetworkPolicy, full SDNOverlay network, basic routingConsul Connect, CNI plugins
StoragePV/PVC/StorageClass ecosystemDocker volumesHost volumes, CSI plugins
Rolling UpdatesNative, configurableBuilt-in, limited optionsCanary, blue-green, rolling
Service DiscoveryCoreDNS + kube-proxyBuilt-in DNS (VIP)Consul (first-class)
RBACGranular, matureBasic Swarm secretsACL policies via Consul/Vault
EcosystemEnormous (CNCF, Helm, Operators)Small, Docker-centricHashiCorp stack integration
Managed CloudEKS, GKE, AKS, DOKS, and moreNot offered by major cloudsHCP Nomad (HashiCorp Cloud)
Resource OverheadHigh (~2GB control plane)Minimal (embedded in Docker)Low (~256MB single agent)
Best ForLarge-scale microservices, cloud-nativeSimple teams, small scaleMixed workloads (containers + legacy)
Choose Kubernetes when…
  • Running 10+ microservices that need independent scaling
  • Production workloads requiring zero-downtime deploys and auto-rollback
  • Teams need fine-grained RBAC and namespace-level resource quotas
  • You want the richest ecosystem (Helm, Operators, service mesh, Prometheus)
  • You are deploying to AWS, GCP, or Azure with managed control plane offerings
Choose Docker Swarm when…
  • Small team with simple Docker-based apps running on 1-5 servers
  • You want zero additional tooling beyond Docker Engine
  • Quick setup is more valuable than advanced features
  • Migrating from Docker Compose to basic orchestration with minimal relearning
  • Limited budget for DevOps expertise
Choose HashiCorp Nomad when…
  • Mixed workloads: containers AND legacy binaries, Java JARs, or VMs
  • Already invested in the HashiCorp stack (Consul, Vault, Terraform)
  • You need Kubernetes-grade features with significantly lower operational overhead
  • Edge deployments or multi-datacenter workloads across heterogeneous infrastructure
  • Compliance requires workload isolation without full Kubernetes complexity

Complete Production Application Example

The following manifest deploys a Node.js API with PostgreSQL, Ingress, and HPA in a single file. Apply with kubectl apply -f complete-app.yaml.

# complete-app.yaml

# 1. Namespace
apiVersion: v1
kind: Namespace
metadata:
  name: myapp
  labels:
    environment: production
---
# 2. ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
  namespace: myapp
data:
  NODE_ENV: production
  PORT: "3000"
  LOG_LEVEL: info
---
# 3. Secret
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: myapp
type: Opaque
stringData:
  DATABASE_URL: postgres://app:secret@postgres:5432/myapp
  JWT_SECRET: change-me-in-production
---
# 4. PostgreSQL StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: myapp
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16-alpine
          env:
            - { name: POSTGRES_DB, value: myapp }
            - { name: POSTGRES_USER, value: app }
            - { name: POSTGRES_PASSWORD, value: secret }
          volumeMounts:
            - { name: pg-data, mountPath: /var/lib/postgresql/data }
          resources:
            requests: { memory: 256Mi, cpu: 250m }
            limits:   { memory: 512Mi, cpu: "1" }
  volumeClaimTemplates:
    - metadata:
        name: pg-data
      spec:
        accessModes: [ReadWriteOnce]
        resources:
          requests:
            storage: 10Gi
---
# 5. Postgres Service
apiVersion: v1
kind: Service
metadata:
  name: postgres
  namespace: myapp
spec:
  type: ClusterIP
  selector:
    app: postgres
  ports:
    - { port: 5432, targetPort: 5432 }
---
# 6. App Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
  namespace: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  strategy:
    type: RollingUpdate
    rollingUpdate: { maxSurge: 1, maxUnavailable: 0 }
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
        - name: app
          image: my-registry/web-app:1.0.0
          ports:
            - { containerPort: 3000 }
          envFrom:
            - configMapRef: { name: app-config }
            - secretRef:    { name: app-secrets }
          resources:
            requests: { memory: 128Mi, cpu: 100m }
            limits:   { memory: 256Mi, cpu: 500m }
          livenessProbe:
            httpGet: { path: /health, port: 3000 }
            initialDelaySeconds: 15
          readinessProbe:
            httpGet: { path: /ready, port: 3000 }
            initialDelaySeconds: 5
---
# 7. App Service
apiVersion: v1
kind: Service
metadata:
  name: web-app-svc
  namespace: myapp
spec:
  type: ClusterIP
  selector:
    app: web-app
  ports:
    - { port: 80, targetPort: 3000 }
---
# 8. Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-app-ingress
  namespace: myapp
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts: [myapp.example.com]
      secretName: myapp-tls
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web-app-svc
                port: { number: 80 }
---
# 9. HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-app-hpa
  namespace: myapp
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  minReplicas: 3
  maxReplicas: 15
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

Verify the Deployment

# Apply the entire stack at once
kubectl apply -f complete-app.yaml

# Verify all resources are created
kubectl get all -n myapp

# Watch pods reach Running state
kubectl get pods -n myapp -w

# Stream application logs
kubectl logs -n myapp -l app=web-app --tail=50 -f

# Test connectivity from inside the cluster
kubectl run test --image=curlimages/curl:latest --rm -it \
  --restart=Never -n myapp \
  -- curl http://web-app-svc/health

# Port-forward for local testing
kubectl port-forward -n myapp svc/web-app-svc 8080:80
# Open http://localhost:8080

# Check HPA status
kubectl get hpa -n myapp

# Check TLS certificate (cert-manager)
kubectl get certificate -n myapp
kubectl describe certificate myapp-tls -n myapp

Try our related developer tools

JSON Formatter · YAML Validator · Base64 Encoder · JWT Decoder · Cron Expression Generator

Frequently Asked Questions

What is the difference between a Pod and a container in Kubernetes?

A container is a single process with its own filesystem and process namespace. A Pod is Kubernetes abstraction layer that wraps one or more containers sharing the same network namespace and storage volumes. Containers inside a Pod communicate via localhost. In most cases a Pod contains one container, but the sidecar pattern uses multiple containers — for example, an app container plus an Envoy proxy for service mesh traffic management.

How do I debug a CrashLoopBackOff Pod?

CrashLoopBackOff means the container keeps crashing and Kubernetes keeps restarting it with exponential back-off. Debug with: (1) kubectl describe pod to read the Events section for error messages, (2) kubectl logs --previous to see logs from the last crashed instance, (3) kubectl exec -it to shell into the running container if it starts briefly, (4) check resource limits — OOMKilled means the container exceeded its memory limit.

What is Helm and when should I use it?

Helm is a package manager for Kubernetes. It bundles related Kubernetes manifests into reusable, versioned packages called charts. Use Helm when: (1) you deploy complex applications with 5+ interdependent resources, (2) you need per-environment configuration (dev, staging, prod) without duplicating manifests, (3) you want to install community-maintained software (PostgreSQL, Redis, NGINX Ingress, Prometheus) from Artifact Hub. For simple single-service apps, raw kubectl apply is often sufficient.

How much does running Kubernetes cost?

Kubernetes itself is free. Cluster costs include: managed control plane fees ($70-150/month on EKS/GKE/AKS), worker node compute ($50-500+/month depending on workload), load balancers ($15-20/month each), and persistent storage ($0.10-0.20/GB/month). A minimal production cluster on EKS might run $200-400/month. Lightweight alternatives like K3s on a single VPS can cost as little as $10-20/month.

What is the difference between a Deployment and a StatefulSet?

Deployments are for stateless applications where Pods are interchangeable. StatefulSets are for stateful applications (databases, queues) where Pods need: (1) stable, unique network identities (pod-0, pod-1, pod-2), (2) stable persistent storage that follows the Pod when rescheduled, (3) ordered, graceful deployment and scaling. Use Deployments for web servers and APIs; use StatefulSets for PostgreSQL, MongoDB, Kafka, Elasticsearch, and ZooKeeper.

How does Kubernetes handle zero-downtime deployments?

Kubernetes Deployments use a RollingUpdate strategy by default. With maxUnavailable: 0 and maxSurge: 1, Kubernetes creates one new Pod with the updated image, waits for it to become Ready (pass readiness probes), then terminates one old Pod, and repeats until all replicas are updated. Combined with pre-stop hooks and a terminationGracePeriodSeconds, running requests complete before the old Pod is killed, achieving true zero-downtime deploys.

What managed Kubernetes service should I choose: EKS, GKE, or AKS?

Google GKE is generally considered the most feature-complete and operationally excellent — unsurprisingly, since Google invented Kubernetes. AWS EKS integrates deeply with the AWS ecosystem (IAM, ALB, EBS) and is the best choice if you are already all-in on AWS. Azure AKS is the natural choice for Microsoft-centric shops using Azure AD and Azure DevOps. All three provide autopilot/serverless node management modes that further reduce operational overhead.

How do I expose a Kubernetes application to the internet?

You have three options: (1) NodePort: exposes the service on a static port on each node IP — simplest but not production-grade, (2) LoadBalancer: provisions a cloud load balancer automatically — simplest for cloud environments but creates one LB per service (expensive), (3) Ingress: a single entry point that routes HTTP/HTTPS traffic to multiple services based on host and path rules — the recommended production pattern. Use an Ingress controller (NGINX, Traefik, AWS ALB Ingress) and cert-manager for automatic TLS certificates.

Conclusion

Kubernetes is one of the most transformative technologies in modern infrastructure, but it comes with genuine complexity. The key insight is to learn it incrementally: start with Pods and Deployments on a local cluster (minikube or kind), add Services and Ingress, then graduate to Helm for packaging, HPA for autoscaling, and PersistentVolumes for stateful workloads. In production, use a managed service (EKS, GKE, AKS) so you can focus on your application rather than cluster operations. The investment in learning Kubernetes pays dividends for years — it is a foundational skill for any developer working in cloud-native environments.

𝕏 Twitterin LinkedIn
Was dit nuttig?

Blijf op de hoogte

Ontvang wekelijkse dev-tips en nieuwe tools.

Geen spam. Altijd opzegbaar.

Try These Related Tools

{ }JSON Formatter

Related Articles

Docker Commands: Complete Guide from Basics to Production

Master Docker with this complete commands guide. Covers docker run/build/push, Dockerfile, multi-stage builds, volumes, networking, Docker Compose, security, registry, and production deployment patterns.

Nginx Config Guide: Write nginx.conf Files — Complete Guide

Master nginx.conf configuration from scratch. Covers nginx.conf structure (main, events, http, server, location blocks), static file serving, reverse proxy with proxy_pass, SSL/TLS with Let's Encrypt, HTTP/2, load balancing (round-robin, least_conn, ip_hash), gzip compression, proxy_cache, rate limiting with limit_req, security headers (HSTS, CSP, X-Frame-Options), access/error logging, and common debugging techniques.

SSH Key Generator: Generate and Manage SSH Keys — Complete Guide

Master SSH key generation and management. Complete guide with ssh-keygen, Ed25519/RSA comparison, SSH config, node-forge, Python paramiko, Git SSH setup, tunneling, certificate-based SSH, and security best practices.