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 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: 5Service 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: ClusterIPIngress 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: 80Configuration 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.jsonHorizontal 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: MaxRolling 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 updateRollback 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-appNetwork 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: 53Persistent Storage#
PersistentVolumeClaim#
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-app-storage
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 10GiStatefulSet 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: 50GiMonitoring 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: 30sPrometheusRule 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 loopingCI/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-appHelm 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: 70Conclusion#
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