You can spin up a cluster with Argo CD in 15 minutes. But if you deploy a Kubernetes workload that vanishes at 2 AM, or a rogue dev spins up an unbounded PVC until the bill explodes, or a security audit turns up privileged: true on every namespace, your environment is not production — it’s chaos in a config file. This checklist evolved from real incidents at fintech and SaaS platforms. Lint your cluster against it before shipping your MVP to prod.
Immediate (Dev & Pr → Staging)
Set CPU and memory requests equal to or slightly below the complementary limit to avoid "requests ceiling" throttling. Validate with CPU Throttling dashboards. How →
With a minAvailable ≥ 1 across 2+ AZ; test by draining a node to confirm eviction decisions.
Each team gets a Role + RoleBinding tight to their namespace. Non-namespace cosmos use ClusterRole tied to labels.
Resources excluded: nodes, persistentvolumes, namespaces.
Start with
0.0.0.0/0 egress allowed; tighten in staging UAT; then move to Cloud Network Firewall (CNF) trie at infra level.
Pod Security Admission with enforce=restricted. Warn on baseline + non-root FSGroup. Map checks: no hostPath, no hostNetwork, readOnlyRootFilesystem=true.
Validate PVC dynamic provisioner + StorageClass mapping. Verify restore from snapshot in dev. Coverage: EBS, Azure Disk, GCE PD.
Use average 95th percentile pre-prod latency as probe initialDelaySeconds ± 3 seconds per deployment stage.
HPA min 1 → max 8 replicas; ClusterAutoscaler scale up delay 10s; Scale down stabilization window 300s; PodDisruptionBudget minAvailable=ceil(max(HPA-min,1)). Scheduler config: select
node.kubernetes.io/instance-type memory > 16GiB.
Production (Go-Live)
Audit mode > deny all; enforcement mode in staging; production blocklists: Scapy, Metasploit, Tor exit nodes, and common mining traffic ASNs.
Tips for cilium: use
k8s:io.kubernetes.pod.namespace in egress rules to only whitelist namespaces that legitimately need internet.
cluster-issuer -> certificate with solvers: http01 for DNS-01 prod staging gapfill; dns01 route53 recordsets; test issuance then roll.
limits.cpu, limits.memory, requests.cpu, requests.memory; quotas: CPU 64 cores, memory 256 GiB, pods 512; enforcement via ResourceQuota + LimitRange; OPA policy to veto speculative resource lines.
kubectl drain --dry-run=server --selector=role=build, topspin rollback script in chat.
Run one
kubectl delete or --force --grace-period=0 on a named deployment each sprint to ensure PDB + kube-controller-manager jointly do their jobs.
Monitoring & Runbooks
15 · Runbook per deployment + prometheus-operator alertsExample runbook: customer-facing rate-limit breach → flip circuit-breaker flag to true → roll back to last green image → notify Slack.
Pin context to prod-default, cannot
kubectl apply -f in dev unless use kube-shell-envrc --context=prod-default.
All constraints come from Git takes over Argo CD syncs; no
kubectl apply config drift; examples: require label team, block top: pods access.
Security & Governance
18 · Named service accounts + least privilege RBAC App can use service accountsa-<app-name> with RoleBinding tight to the namespace only.
podAntiAffinity.preferredDuringSchedulingIgnoredDuringExecution: topologyKey=topology.kubernetes.io/zone
Attestations pushed to registry on build; Kyverno verifies signature in admission controller; reject unsigned.
Audit (Every quarter)
Automate this with kubectl get --raw /metrics | kube-score | kubescape in a CronJob and post results to a compliance dashboard.
Gate deployments from
.securityContext: runAsNonRoot=true runAsUser=1000 fsGroup=2000; kube-bench run weekly + diff.
Resource Heat Map
22 · Cluster-wide resource heat map dashboardUse bin-packing metrics: resource_debt = ceiling((sum(item.requests) - node.allocatable)/node.capacity); daily dashboards; P1 alert when >30% for 1h.
ingress.spec.tls.hosts[].secretName valid; nginx.ingress.kubernetes.io/ssl-redirect: "true"; modern cipher suites enabled (TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256).
VolumeSnapshotClass standard mapped to StorageClass; snapshot every 6h; retain 14 days; restore into -clone namespace for validation.
CAN/UAT sets → prod staging → prod A → prod B → prod C; one AZ at a time; monitor NodeCon; rollback via Azure/GCP snapshot if degradation >1%. Roll out rollback in 5 min cycle per AZ max latency.
Operational Excellence (One-pager per service)
26 · SLA dashboard: overall, per-service, per-AZInherits from ServiceLevelObjective
availability=0.9995; alert SLO burn SLO < 0.95; Ticket ≥ 15 min total incident duration triggers P1.
S1: PR -> preview (Pull request environment) S2: main -> dev (CI environment) S3: promotion to stage (performance regression gate) S4: promotion to prod (staging root certs).
Gate on every stage: kube-score, trivy, kubescape, Argo CD health.
Use semantic-versioning image tags (
v2.1.3) with imagePullPolicy=Always so Argo CD always syncs fresh hash even when the YML hasn’t changed.
mTLS strict; rate-limiting 1000 RPS per upstream host; Cilium Hubble for per-service latency; zero-downtime upgrade gate: 10% traffic → 50% → 100%.
kyverno enforces cost cap quotas; Kubecost or VMware Velero cost-exporter for CSI / Pod cost attribution.
Cordon/punt pods exceeding €50/month projected cost within next 7 days.
Your Turn: The 80-minute turborepo
Pick one environment (dev → staging OR staging → prod).
1) Onboard a new namespace: namespace: demo-app
2) Run the linter: kube-score score ./k8s
3) Fix the top-5 red items from the dashboard
4) Apply one NetworkPolicy with namespaceSelector to demo-app
5) Rerun kube-score -> green
6) Ship with Argo CD.
Checkout the companion repo github.com/tayoca/k8s-production-checklist-sample with everything baked into a GitHub Action that fails your PR on kube-score reds and applies sample NetworkPolicies.
TL;DR – Use the List, Fix the Cluster
Print this checklist, schedule a 50-minute brown-bag with your platform team, and walk through each item. If you hit a red item, you just found your production up grade work bench.