Deploying to Kubernetes — From Docker Image to Running Service

Why Deploying to Kubernetes Matters

Knowing how to deploy applications to Kubernetes is a critical skill for modern DevOps and platform engineers. This chapter walks through the complete deployment workflow — from building a Docker image to running a scalable, production-grade service on Kubernetes.

Why this matters for your career:

  • Deployment automation is the foundation of DevOps and CI/CD
  • Kubernetes deployment skills are required for most cloud engineering roles
  • Understanding the deployment lifecycle helps you debug production issues
  • Freelance infrastructure projects often involve migrating to or from Kubernetes

The Deployment Workflow

Code → Docker Image → Push to Registry → Kubernetes Manifest → kubectl apply → Running Service

Step 1: Build a Docker Image

# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/server.js"]

Build and push:

# Build
docker build -t my-app:latest .

# Tag for registry
docker tag my-app:latest ghcr.io/myorg/my-app:v1.0.0

# Push to container registry
docker push ghcr.io/myorg/my-app:v1.0.0

Step 2: Write Kubernetes Manifests

# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: my-app
---
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: my-app
spec:
  replicas: 3
  revisionHistoryLimit: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: app
        image: ghcr.io/myorg/my-app:v1.0.0
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: app-secrets
              key: database-url
        resources:
          requests:
            cpu: "200m"
            memory: "256Mi"
          limits:
            cpu: "500m"
            memory: "512Mi"
        livenessProbe:
          httpGet:
            path: /health
            port: 3000
          initialDelaySeconds: 10
          periodSeconds: 30
        readinessProbe:
          httpGet:
            path: /ready
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 10
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
  namespace: my-app
spec:
  selector:
    app: my-app
  ports:
  - port: 80
    targetPort: 3000
  type: ClusterIP
---
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  namespace: my-app
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-app-service
            port:
              number: 80
  tls:
  - hosts:
    - myapp.example.com
    secretName: myapp-tls

Step 3: Apply to Cluster

kubectl apply -f namespace.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml

# Or apply all at once
kubectl apply -f k8s/

# Check status
kubectl get pods -n my-app -w
kubectl get deployments -n my-app
kubectl get ingress -n my-app

Rolling Updates and Rollbacks

Rolling Update

# Update to new version
kubectl set image deployment/my-app app=ghcr.io/myorg/my-app:v2.0.0 -n my-app

# Or update the manifest and re-apply
kubectl apply -f deployment.yaml

# Monitor rollout
kubectl rollout status deployment/my-app -n my-app

Rollback

# Undo last rollout
kubectl rollout undo deployment/my-app -n my-app

# Rollback to specific revision
kubectl rollout undo deployment/my-app --to-revision=2 -n my-app

# View rollout history
kubectl rollout history deployment/my-app -n my-app

Secrets and ConfigMaps

# Create ConfigMap from literal values
kubectl create configmap app-config -n my-app \
  --from-literal=APP_NAME=my-app \
  --from-literal=LOG_LEVEL=info

# Create Secret from literal values
kubectl create secret generic app-secrets -n my-app \
  --from-literal=database-url=postgresql://user:pass@host/db

# Or from a file
kubectl create secret generic app-secrets -n my-app \
  --from-file=./secrets.env

Deploying with Helm

Helm is the package manager for Kubernetes:

# Add a repo
helm repo add bitnami https://charts.bitnami.com/bitnami

# Install a chart
helm install my-release bitnami/nginx

# Create your own chart
helm create my-chart

# Install your chart
helm install my-app ./my-chart \
  --set image.tag=v1.0.0 \
  --set replicaCount=3 \
  -n my-app

# Upgrade
helm upgrade my-app ./my-chart --set image.tag=v2.0.0

# Rollback
helm rollback my-app 1

Deployment Strategies Comparison

| Strategy | Description | Zero Downtime | Rollback | Use Case | |----------|-------------|--------------|----------|----------| | RollingUpdate | Replace pods gradually | ✅ Yes | ✅ Yes | Default for most apps | | Recreate | Kill all pods, then create new | ❌ No | ⚠️ Manual | Stateful apps, DB migrations | | Blue/Green | Two environments, switch traffic | ✅ Yes | ✅ Instant | Critical production apps | | Canary | Gradual traffic shift to new version | ✅ Yes | ✅ Fast | Testing with real traffic | | A/B Testing | Route based on headers/cookies | ✅ Yes | ✅ Fast | Feature experimentation |

Common Troubleshooting Commands

| Command | Purpose | |---------|--------| | kubectl describe pod pod-name -n my-app | Detailed pod status and events | | kubectl logs pod-name -n my-app --tail=100 | View last 100 log lines | | kubectl logs pod-name -n my-app -f | Stream logs in real-time | | kubectl exec -it pod-name -n my-app -- sh | Open shell in container | | kubectl get events -n my-app --sort-by='.lastTimestamp' | View cluster events | | kubectl port-forward pod-name 8080:3000 -n my-app | Tunnel to a pod locally |

Summary

Deploying to Kubernetes involves building a Docker image, writing Kubernetes manifests (Deployment, Service, Ingress), and applying them with kubectl. Rolling updates enable zero-downtime deployments. Helm simplifies package management. Understanding this workflow is essential for any DevOps or platform engineering role.

Key takeaways:

  • Build minimal Docker images using multi-stage builds
  • Write Deployment, Service, and Ingress manifests
  • Use kubectl apply to deploy and rollout for updates
  • RollingUpdate strategy provides zero-downtime deployments
  • ConfigMaps and Secrets manage configuration separately from images
  • Helm charts package and version Kubernetes applications
  • Blue/Green and Canary strategies reduce deployment risk
  • Use describe, logs, exec for troubleshooting

What's Next: Monitoring & Scaling

The next chapter covers monitoring and scaling in Kubernetes — metrics, HPA (Horizontal Pod Autoscaler), resource quotas, and cluster monitoring with Prometheus.

Unlock Full Tutorial

This chapter is paid content. Join the project to unlock over 5000 words of deep analysis, including 10+ god-tier Prompts and real Source Code examples!