Most Kubernetes outages do not begin with a dramatic crash. They begin quietly: a pod receives traffic too early, a container runs out of memory, or a rollout replaces every replica at once. The deployment looked fine. The release was not.
The good news is that you do not need a huge YAML file or a complicated setup to avoid these problems. A few simple Kubernetes deployment best practices can make your app safer, easier to run, and much less stressful to ship.
1. Use replicas to keep your app available
A single pod is fine for testing. In production, it is a risk.
Replicas give you basic availability. If one pod fails or the node has a problem, another pod can keep serving traffic. For most production apps, start with at least 2 replicas.
2. Use all three probes
Think of probes as your app’s health signals.
Readiness probe says, “Can I receive traffic?”
Liveness probe says, “Am I still healthy?”
Startup probe says, “Give me time to boot.”
If your app starts slowly, the startup probe is especially helpful because it prevents Kubernetes from restarting the container too early. This is especially useful for Java or Spring Boot apps that take a little longer to warm up.
3. Set CPU and memory requests and limits
This is one of the easiest ways to make a deployment stable.
Requests tell Kubernetes what your app needs. Limits tell it how much your app is allowed to use. Together, they help the scheduler place pods properly and protect the cluster from one app taking too many resources.
4. Use namespaces to stay organized
Namespaces keep your cluster clean and easy to manage.
A simple setup is to separate environments like development, staging, and production into different namespaces. That makes access control, debugging, and resource tracking much easier.
5. Prefer RollingUpdate for most releases
For most apps, RollingUpdate is the safest deployment strategy.
It replaces pods gradually, so users keep getting service while the new version comes up. Recreate stops the old version first, which can cause downtime. Use Recreate only when your app cannot safely run two versions together.
6. Use init containers for setup work
Not every task belongs in the main application container.
Init containers are perfect for startup tasks like waiting for a database, checking dependencies, or preparing files. They keep the main container cleaner and make startup logic easier to understand.
7. Add node affinity when placement matters
Sometimes your app should run on a specific type of node.
Node affinity helps you tell Kubernetes where the pod should go, such as nodes with more memory, a special disk, or a specific zone. Use it when performance or reliability depends on where the pod runs.
8. Do not forget the small but important extras
A few simple habits go a long way:
Use versioned image tags like
v1.0.0instead oflatestKeep non-secret config in ConfigMaps and sensitive values in Secrets
Add graceful shutdown so requests can finish before the pod stops
Add a PodDisruptionBudget if the app is critical, so Kubernetes does not evict too many pods at once
Use Horizontal Pod Autoscaling if traffic changes often, so the app can scale up and down automatically
A simple Kubernetes deployment example
Here is a beginner-friendly example that brings the ideas together.
apiVersion: v1
kind: Namespace
metadata:
name: production
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: production
spec:
replicas: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
terminationGracePeriodSeconds: 30
# Run setup before the app starts.
initContainers:
- name: wait-for-db
image: busybox:1.36
command: ["sh", "-c", "until nc -z db 5432; do echo waiting for db; sleep 2; done"]
containers:
- name: web-app
image: myrepo/web-app:v1.0.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
startupProbe:
httpGet:
path: /startup
port: 8080
failureThreshold: 30
periodSeconds: 10
# Schedule this app on production nodes.
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: workload
operator: In
values:
- productionTogether, these settings give you safer releases, better scheduling, and fewer surprises when traffic arrives.
If you are using Spring Boot, map the probe paths to Actuator endpoints. If you are using another framework, just replace them with your own health URLs. The idea stays the same: tell Kubernetes when your app is ready, alive, and still starting.
Final thoughts
A good Kubernetes deployment does not need to be fancy. It needs to be safe, readable, and predictable.
If you get the basics right, replicas, probes, resource limits, namespaces, rollout strategy, init containers, and node affinity, you will already avoid many of the problems that cause production pain.
