Deployments Best Practices
This is a working checklist for running stateless workloads on Kubernetes Deployments in production. Each practice is a rule of thumb with the reasoning behind it, grouped by concern.
How to Use This List
Treat these as defaults, not dogma. Adopt them unless you have a specific, documented reason to deviate.
Apply them at review time on every new Deployment and when auditing existing ones.
The groups move from structure, to updates, to reliability, to security, to operations. Start at the top; the earlier groups are the ones most often gotten wrong.
A - Structure and Ownership
-
One Deployment per microservice. Keep each service in its own Deployment so it scales, updates, and rolls back independently. Bundling unrelated containers into one Pod couples their lifecycles.
-
Never run naked Pods. A bare Pod has no controller, so a node failure loses it forever. Always let a Deployment (or another workload controller) own your Pods.
-
Match selectors to template labels exactly.
spec.selector.matchLabelsmust equal the Pod template labels, and the selector is immutable after creation. Get it right the first time or you must recreate the Deployment. -
Use stable, meaningful labels. Standardize on labels like
app,version, andapp.kubernetes.io/nameso Services, dashboards, and policies can select workloads consistently. -
Pick the right controller for the job. Deployments are for stateless apps. Use StatefulSet for stable identity and storage, DaemonSet for one Pod per node, and Job or CronJob for run-to-completion work.
B - Images and Updates
-
Pin images by digest, not floating tags. A digest (
image@sha256:...) is immutable, so every node runs identical bytes and rollbacks are exact. Avoidlatest, which is ambiguous and breaks reproducibility. -
Always set a real readiness probe. Without it, rolling updates count Pods as available on container start and can route traffic to a Pod that cannot serve. This is the most common cause of "successful" deploys that drop requests.
-
Tune maxSurge and maxUnavailable deliberately. The 25% defaults are rarely ideal. For zero-capacity-loss set
maxUnavailable: 0with surge headroom; use absolute numbers below about 10 replicas to avoid rounding surprises. -
Record a change-cause on every rollout. Set the
kubernetes.io/change-causeannotation sorollout historyis readable during an incident. -
Keep revision history for rollback. Leave
revisionHistoryLimitat a nonzero value (default 10) so you can roll back more than one step. Setting it to zero disables rollback.
C - Reliability and Availability
-
Spread replicas across failure domains. Use topology spread constraints or anti-affinity across nodes and zones. Three replicas on one node give zero fault tolerance for node failure.
-
Set a PodDisruptionBudget. A PDB bounds voluntary disruptions like node drains, so an upgrade cannot take down all your replicas at once. It is separate from, and complements, the rolling update knobs.
-
Handle graceful shutdown. Add a
preStophook and adequateterminationGracePeriodSecondsso retiring Pods drain in-flight requests after leaving Service endpoints. -
Set resource requests and limits. Requests drive sane scheduling; a memory limit protects nodes from a leaking container. Many teams set a memory limit but omit CPU limits to avoid throttling.
-
Use a HorizontalPodAutoscaler for load-driven scaling. Let the HPA adjust
replicasfrom metrics rather than hardcoding a fixed count, and do not fight it with manual scaling.
D - Security Defaults
-
Run as non-root. Set
runAsNonRoot: trueand a non-zero UID so a container escape does not start as root on the node. -
Drop all capabilities and block privilege escalation. Set
allowPrivilegeEscalation: falseandcapabilities.drop: ["ALL"], adding back only what the app truly needs. -
Use a read-only root filesystem. Set
readOnlyRootFilesystem: trueand mount anemptyDirfor any writable path to limit tampering. -
Meet the restricted Pod Security Standard. Target the restricted profile with
seccompProfile: RuntimeDefaultso the namespace policy admits your Pods cleanly. -
Scan and sign images. Gate on a scanner like Trivy or Grype and verify signatures with cosign so only vetted images reach the cluster.
E - Operations and GitOps
-
Deploy declaratively from Git. Keep manifests in version control and apply through Argo CD or Flux so the cluster state is auditable and reproducible.
-
Do not hand-edit GitOps-owned objects. A manual
kubectl editorrollout undoon an Argo CD-managed Deployment gets reverted at the next sync. Change Git instead. -
Bound rollouts with progressDeadlineSeconds. A stuck rollout should fail visibly rather than hang, so your pipeline can detect
ProgressDeadlineExceededand act. -
Remember containerd runs your Pods. Nodes run containers via containerd through the CRI. Use Docker Engine for build and local dev, never as the in-cluster runtime.
-
Watch rollouts, not just apply results. Use
kubectl rollout statusin CI so a deploy job fails when Pods do not become ready, instead of reporting success onapply.
When You Are Done
You should be able to answer yes to a few questions for any Deployment you own.
Does it have a readiness probe, resource requests, and a security context that meets the restricted standard?
Is it spread across failure domains with a PodDisruptionBudget, and can you roll it back to a known-good revision?
Is it declared in Git and deployed through your GitOps controller rather than by hand?
FAQs
Why avoid naked Pods entirely? A bare Pod has no controller to recreate it, so any node failure or eviction loses it permanently. A Deployment restores desired state.
Should every Deployment have limits? Set a memory limit to protect the node. CPU limits are optional and often omitted to avoid throttling, but always set requests.
One Deployment per service or per team? Per service. Independent Deployments let each service scale, update, and roll back without disturbing its neighbors.
Do I need both a rolling update config and a PodDisruptionBudget? Yes. The rolling update knobs govern deploys; the PDB governs voluntary disruptions like drains. They protect different events.
Is Docker used inside the cluster? No. Nodes run containers via containerd over the CRI. Docker Engine is for building images and local development.
Related
- Deployments Basics
- How Rolling Updates Actually Work
- Rolling Update Parameters
- Rollback
- Blue/Green & Canary
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).