Back to Blog
6 min read

Kubernetes Deployment Guide: From Development to Production

Learn how to deploy applications to Kubernetes with best practices for configuration, scaling, monitoring, and security in production environments.

#Kubernetes#DevOps#Docker#Cloud#Infrastructure
Kubernetes Deployment Guide: From Development to Production

Kubernetes has become the standard for container orchestration. This guide covers deploying applications to Kubernetes with production-ready configurations, from basic deployments to advanced patterns.

Kubernetes Core Concepts#

Before diving into deployments, understand these key concepts:

  • Pod - Smallest deployable unit, contains one or more containers
  • Deployment - Manages ReplicaSets and provides declarative updates
  • Service - Exposes pods to network traffic
  • ConfigMap/Secret - External configuration and sensitive data
  • Ingress - HTTP routing and load balancing

Kubernetes follows a declarative model—you describe the desired state, and Kubernetes works to maintain it.

Basic Deployment#

Deployment Manifest#

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  labels:
    app: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
        - name: my-app
          image: myregistry/my-app:1.0.0
          ports:
            - containerPort: 3000
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "256Mi"
              cpu: "500m"
          env:
            - name: NODE_ENV
              value: "production"
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: my-app-secrets
                  key: database-url
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /ready
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 5

Service Configuration#

# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: ClusterIP

Ingress for External Access#

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/rate-limit: "100"
spec:
  tls:
    - hosts:
        - myapp.example.com
      secretName: my-app-tls
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app-service
                port:
                  number: 80

Configuration Management#

ConfigMaps#

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-app-config
data:
  APP_NAME: "My Application"
  LOG_LEVEL: "info"
  CACHE_TTL: "3600"
  config.json: |
    {
      "features": {
        "newUI": true,
        "analytics": true
      },
      "limits": {
        "maxUploadSize": 10485760
      }
    }

Secrets#

# secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: my-app-secrets
type: Opaque
stringData:
  database-url: "postgresql://user:pass@host:5432/db"
  api-key: "sk_live_xxxxx"
  jwt-secret: "your-jwt-secret"

Using ConfigMaps and Secrets#

# deployment.yaml (partial)
spec:
  containers:
    - name: my-app
      envFrom:
        - configMapRef:
            name: my-app-config
        - secretRef:
            name: my-app-secrets
      volumeMounts:
        - name: config-volume
          mountPath: /app/config
          readOnly: true
  volumes:
    - name: config-volume
      configMap:
        name: my-app-config
        items:
          - key: config.json
            path: config.json

Horizontal Pod Autoscaling#

# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-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
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
        - type: Percent
          value: 100
          periodSeconds: 15
        - type: Pods
          value: 4
          periodSeconds: 15
      selectPolicy: Max

Rolling Updates and Rollbacks#

Deployment Strategy#

# deployment.yaml (partial)
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%        # Max pods above desired count
      maxUnavailable: 25%  # Max pods unavailable during update

Rollback Commands#

# View rollout history
kubectl rollout history deployment/my-app
 
# Rollback to previous version
kubectl rollout undo deployment/my-app
 
# Rollback to specific revision
kubectl rollout undo deployment/my-app --to-revision=2
 
# Check rollout status
kubectl rollout status deployment/my-app

Network Policies#

# network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: my-app-network-policy
spec:
  podSelector:
    matchLabels:
      app: my-app
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: production
        - podSelector:
            matchLabels:
              app: nginx-ingress
      ports:
        - protocol: TCP
          port: 3000
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: postgres
      ports:
        - protocol: TCP
          port: 5432
    - to:
        - podSelector:
            matchLabels:
              app: redis
      ports:
        - protocol: TCP
          port: 6379
    # Allow DNS
    - to:
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53

Persistent Storage#

PersistentVolumeClaim#

# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-app-storage
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 10Gi

StatefulSet for Databases#

# statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16
          ports:
            - containerPort: 5432
          env:
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-secrets
                  key: password
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 50Gi

Monitoring with Prometheus#

ServiceMonitor#

# servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: my-app-monitor
  labels:
    release: prometheus
spec:
  selector:
    matchLabels:
      app: my-app
  endpoints:
    - port: http
      path: /metrics
      interval: 30s

PrometheusRule for Alerts#

# prometheusrule.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: my-app-alerts
spec:
  groups:
    - name: my-app
      rules:
        - alert: HighErrorRate
          expr: |
            sum(rate(http_requests_total{app="my-app",status=~"5.."}[5m])) /
            sum(rate(http_requests_total{app="my-app"}[5m])) > 0.05
          for: 5m
          labels:
            severity: critical
          annotations:
            summary: High error rate detected
            description: Error rate is above 5% for 5 minutes
 
        - alert: PodCrashLooping
          expr: |
            rate(kube_pod_container_status_restarts_total{pod=~"my-app.*"}[15m]) > 0
          for: 5m
          labels:
            severity: warning
          annotations:
            summary: Pod is crash looping

CI/CD Integration#

GitHub Actions Deployment#

# .github/workflows/deploy.yml
name: Deploy to Kubernetes
 
on:
  push:
    branches: [main]
 
env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}
 
jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
 
      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max
 
      - name: Set up kubectl
        uses: azure/setup-kubectl@v3
 
      - name: Configure kubectl
        run: |
          echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
          export KUBECONFIG=kubeconfig
 
      - name: Deploy to Kubernetes
        run: |
          kubectl set image deployment/my-app \
            my-app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          kubectl rollout status deployment/my-app

Helm Chart Structure#

my-app/
├── Chart.yaml
├── values.yaml
├── values-production.yaml
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── secret.yaml
│   ├── hpa.yaml
│   └── _helpers.tpl

values.yaml#

# values.yaml
replicaCount: 3
 
image:
  repository: myregistry/my-app
  tag: latest
  pullPolicy: IfNotPresent
 
service:
  type: ClusterIP
  port: 80
 
ingress:
  enabled: true
  className: nginx
  hosts:
    - host: myapp.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: my-app-tls
      hosts:
        - myapp.example.com
 
resources:
  limits:
    cpu: 500m
    memory: 256Mi
  requests:
    cpu: 100m
    memory: 128Mi
 
autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 20
  targetCPUUtilizationPercentage: 70

Conclusion#

Kubernetes provides powerful tools for deploying and managing containerized applications at scale. By following these best practices—proper resource management, health checks, autoscaling, and security policies—you can build resilient, production-ready deployments.

Key takeaways:

  • Always set resource requests and limits
  • Implement health checks (liveness and readiness probes)
  • Use ConfigMaps and Secrets for configuration
  • Implement HPA for automatic scaling
  • Use Network Policies for security
  • Monitor with Prometheus and set up alerts
Kubernetes Documentation
0 views
More Articles