A comprehensive guide to deploying, scaling, and operating applications on Kubernetes. Covers deployment strategies, resource management, security, observability, and GitOps workflows.
3. Pod Security
5. Autoscaling
10. Observability
11. GitOps Workflows
The default Kubernetes deployment strategy. Gradually replaces old pods with new ones, maintaining availability throughout the update.
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Allow 1 extra pod during update
maxUnavailable: 0 # Never drop below desired countWhen to use: Most applications. Zero-downtime by default. Simple and well-understood.
Key settings:
maxSurge: 1, maxUnavailable: 0 β safest option, but slowest. Each new pod must be ready before an old pod is terminated.maxSurge: 25%, maxUnavailable: 25% β faster, trades some capacity for speed. Good for stateless services with many replicas.Run two identical environments (blue and green). Switch traffic from blue to green atomically by updating the Service selector.
# Blue deployment (current production)
metadata:
name: app-blue
labels:
app: myapp
version: blue
# Green deployment (new version)
metadata:
name: app-green
labels:
app: myapp
version: green
# Service β switch traffic by changing the selector
spec:
selector:
app: myapp
version: green # Flip between "blue" and "green"When to use: When you need instant rollback capability or must validate the full deployment before routing traffic.
Trade-off: Requires 2x resources during the transition period.
Route a small percentage of traffic to the new version. Gradually increase if metrics are healthy.
The simplest approach uses replica ratios: if you have 10 replicas total, set 9 to the old version and 1 to the new version (10% canary). More sophisticated approaches use Istio, Linkerd, or Argo Rollouts for weighted traffic splitting.
# With Argo Rollouts (recommended)
apiVersion: argoproj.io/v1alpha1
kind: Rollout
spec:
strategy:
canary:
steps:
- setWeight: 10 # 10% traffic to canary
- pause: { duration: 5m }
- setWeight: 30
- pause: { duration: 5m }
- setWeight: 60
- pause: { duration: 5m }
canaryService: app-canary
stableService: app-stableWhen to use: High-risk deployments, user-facing services where you need to validate with real traffic before full rollout.
Every container should define both requests and limits:
resources:
requests:
cpu: 250m # 0.25 CPU cores guaranteed
memory: 256Mi # 256 MiB guaranteed
limits:
cpu: "1" # Throttle at 1 core
memory: 512Mi # OOMKill above 512 MiBRules of thumb:
Kubernetes assigns QoS classes based on resource configuration:
| QoS Class | Condition | Eviction Priority |
|---|---|---|
| Guaranteed | requests == limits for all containers | Last to be evicted |
| Burstable | At least one request set | Middle priority |
| BestEffort | No requests or limits set | First to be evicted |
For production workloads, always aim for Guaranteed or Burstable.
Set default resource constraints for a namespace to prevent unbounded pods:
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: app
spec:
limits:
- type: Container
default:
cpu: 500m
memory: 256Mi
defaultRequest:
cpu: 100m
memory: 128Mi
max:
cpu: "4"
memory: 4GiRun containers as non-root with minimal privileges. This prevents container breakout attacks and limits the blast radius of compromised pods.
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]Key settings:
runAsNonRoot: true β prevents running as UID 0 even if the image defaults to root.readOnlyRootFilesystem: true β prevents writing to the container filesystem. Use emptyDir volumes for /tmp.capabilities.drop: ["ALL"] β removes all Linux capabilities. Add specific ones back if needed (rare).seccompProfile.type: RuntimeDefault β applies the container runtime's default seccomp profile.Kubernetes 1.25+ includes built-in pod security admission. Apply at the namespace level:
apiVersion: v1
kind: Namespace
metadata:
name: app
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/warn: restrictedThe three levels are:
Kubernetes provides three types of probes. Use all three for production workloads.
Protects slow-starting containers. The kubelet won't run liveness or readiness probes until the startup probe succeeds. This prevents aggressive liveness probes from killing pods that are still initializing.
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30 # 30 Γ 2s = 60 seconds to start
periodSeconds: 2Detects deadlocks and hung processes. If the liveness probe fails, the kubelet restarts the pod.
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 3Warning: Don't make the liveness probe depend on external services (database, cache). If the database is down, restarting the pod won't help β it will create a cascade of restarts.
Controls whether the pod receives traffic from the Service. If readiness fails, the pod is removed from Service endpoints but not restarted.
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 2Design pattern: The /ready endpoint should check that the application can serve requests (database connected, cache warmed, etc.), while /health only checks that the process is alive.
Scale the number of pods based on CPU, memory, or custom metrics.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # Wait 5 min before scaling downKey principles:
minReplicas >= 2 for production (HA).behavior block to prevent flapping. Scale up fast, scale down slowly.Automatically adjusts resource requests/limits based on actual usage. Useful for right-sizing but can conflict with HPA.
Recommendation: Use VPA in "Off" mode to get sizing recommendations, then apply them manually.
Prevent voluntary disruptions (node drain, rolling update) from removing too many pods at once.
apiVersion: policy/v1
kind: PodDisruptionBudget
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/name: appAlways start with a default deny policy, then create allow-list policies for specific traffic. This follows the principle of least privilege.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- Egressspec:
podSelector:
matchLabels:
app: myapp
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- port: 8080
egress:
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- port: 53
protocol: UDPImportant: NetworkPolicies require a CNI that supports them (Calico, Cilium, Weave Net). The default kubenet does not enforce them.
Kubernetes Secrets are base64-encoded, not encrypted. Never commit them to version control.
Recommended approaches:
1. External Secrets Operator β syncs secrets from AWS Secrets Manager, Vault, GCP Secret Manager.
2. Sealed Secrets β encrypt secrets with a cluster-specific key. Safe to commit the encrypted version.
3. SOPS β encrypts YAML files with age, PGP, or cloud KMS. Works with any GitOps tool.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: app-secrets
data:
- secretKey: database-url
remoteRef:
key: prod/app/database-urlOrganize manifests as a base configuration with environment-specific overlays:
βββ base/
β βββ deployment.yaml
β βββ service.yaml
β βββ kustomization.yaml
βββ overlays/
β βββ dev/
β β βββ kustomization.yaml
β βββ prod/
β βββ kustomization.yaml
Use JSON patches for precise field modifications:
patches:
- target:
kind: Deployment
name: app
patch: |-
- op: replace
path: /spec/replicas
value: 5For optional features (monitoring, RBAC) that may or may not be included:
# kustomization.yaml
components:
- ../../components/monitoring
- ../../components/rbac1. Pin chart versions in CI/CD. Never use helm install without --version.
2. Use values files per environment (values-dev.yaml, values-prod.yaml) instead of long --set chains.
3. Validate templates before deploying: helm template . -f values-prod.yaml | kubectl apply --dry-run=server -f -
4. Use named templates (_helpers.tpl) for reusable label blocks and selectors.
5. Set revisionHistoryLimit to avoid accumulating old ReplicaSets.
1. Metrics β Prometheus + Grafana. Use ServiceMonitor for auto-discovery.
2. Logs β Structured JSON logging shipped via Promtail/Fluentbit to Loki or Elasticsearch.
3. Traces β OpenTelemetry SDK β Jaeger or Tempo.
At minimum, configure alerts for:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
source:
repoURL: https://github.com/org/manifests
targetRevision: main
path: overlays/prod
destination:
server: https://kubernetes.default.svc
namespace: app
syncPolicy:
automated:
prune: true
selfHeal: trueapiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: myapp
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: manifests
path: ./overlays/prod
prune: trueKey principle: The Git repository is the source of truth. All changes go through pull requests, are reviewed, and are applied automatically by the GitOps controller.
Before deploying to production, verify each item:
:latestterminationGracePeriodSeconds allows graceful shutdownrevisionHistoryLimit is set to avoid unbounded ReplicaSet history*Part of Kubernetes Manifests Pack β (c) 2026 Datanest Digital (datanest.dev)*
This pack is a production-minded starting point, not a set of manifests to apply unchanged. Replace the sample image, domain, labels, health endpoints, resource profile, and secret workflow, then render and review the complete environment before deployment.
A Deployment declares the desired pods and rolling-update behavior. Its labels must match the selectors used by the Service, HPA, and PDB. A Service gives those replaceable pods a stable cluster address, while an Ingress routes external HTTP traffic to the Service and terminates TLS.
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders
spec:
replicas: 3
selector:
matchLabels: { app: orders }
template:
metadata:
labels: { app: orders }
spec:
containers:
- name: api
image: ghcr.io/example/orders@sha256:REPLACE_ME
ports:
- { name: http, containerPort: 8080 }
---
apiVersion: v1
kind: Service
metadata:
name: orders
spec:
selector: { app: orders }
ports:
- { name: http, port: 80, targetPort: http }A ConfigMap stores non-sensitive settings such as log level and feature flags. A Secret holds credentials, but base64 is not encryption: use External Secrets, Sealed Secrets, or SOPS for production rather than committing clear credentials. Both can be exposed as environment variables or mounted files.
apiVersion: v1
kind: ConfigMap
metadata:
name: orders-config
data:
LOG_LEVEL: "info"
---
apiVersion: v1
kind: Secret
metadata:
name: orders-secrets
type: Opaque
stringData:
DATABASE_URL: "replace-through-secret-manager"An HPA adjusts replica count from observed CPU or memory utilization; requests must be set because utilization is calculated against them. A PDB limits voluntary disruption during node drains or upgrades. It does not protect against crashes and should never make routine maintenance impossible.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: orders
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: orders
minReplicas: 3
maxReplicas: 12
metrics:
- type: Resource
resource:
name: cpu
target: { type: Utilization, averageUtilization: 70 }
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: orders
spec:
maxUnavailable: 1
selector:
matchLabels: { app: orders }An Ingress should name an installed controller, route to a named Service port, and use a real certificate issuer:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: orders
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts: [orders.example.com]
secretName: orders-tls
rules:
- host: orders.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: orders
port: { name: http }Keep one shared base and express differences through overlays/dev/ and overlays/prod/. Development normally uses one replica, smaller requests, debug logging, a non-production hostname, and may disable monitoring or the PDB. Production should use immutable image tags or digests, multiple replicas, stricter resources, autoscaling, TLS, default-deny networking, monitoring, and zone-aware scheduling. Always inspect kubectl kustomize overlays/prod and run a server-side dry run before applying it.
Set CPU and memory requests from measured steady-state demand so the scheduler can place pods accurately. Set limits from load tests: an overly low CPU limit causes throttling, while an overly low memory limit causes OOM kills. Configure startup, readiness, and liveness probes separately. Startup accommodates initialization; readiness controls Service traffic; liveness should only restart a genuinely stuck process.
Spread replicas across nodes with preferred pod anti-affinity:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels: { app: orders }Use a required rule only when the cluster always has enough eligible nodes; otherwise a healthy release can remain Pending.
Raw YAML is transparent, easy to audit, and works well with Kustomize when environments differ only by patches. Helm is better when distributing a reusable application with many optional features, generated names, or consumer-controlled values, but templating adds indirection and rendered output must still be reviewed. This packβs helm/ directory provides metadata and values onlyβthere is no templates/ directoryβso integrate those values into an existing chart or add templates before running helm install. For either approach, commit rendered changes through review and validate them in CI.
Get the full Kubernetes Manifests Pack and unlock everything.
Get the complete guide with every chapter unlocked, including code samples, diagrams, and best practices.
Access all interactive tools with complete data, all workload profiles, and the full scenario library.
Downloadable source code, configuration files, and working examples from every chapter.
Free updates for life. Every new chapter, tool, and improvement included.