Deployment · intermediate

Production environment drift debugging checklist

Production environment drift is a deployment contract failure: the running service no longer matches the reviewed manifest, IaC declaration, or build artifact the release engineer signed off on. This checklist helps release engineers move from observable evidence (works in staging, fails only in production) to a verifiable cause without destructive actions, using read-only inspection of the live target, declarative drift detection, and immutable artifact digests.

The symptoms

  • The same build artifact passes all checks in staging and pre-production, yet fails in production with no code change between environments.
  • Environment variables, mounted ConfigMaps/Secrets, or feature flags on the running production target differ from the values recorded in the reviewed deployment manifest.
  • Failures appear only on a subset of nodes, pods, regions, or AZs even though they all run the identical image tag, suggesting local divergence rather than a code defect.
  • DNS, TLS, timeout, or upstream-connection errors surface exclusively on production traffic paths, while internal synthetic probes against the same host succeed.
  • Language runtime, OS package, or base image version on the production host no longer matches the version pinned in the deployment contract or CI build context.
  • Health and readiness checks pass on rollout, but live requests still receive stale, default, or placeholder configuration values from the running process.

Likely causes

  • Manual edits were applied directly to production hosts, running containers, or control-plane resources, bypassing the reviewed pipeline and creating undocumented divergence.
  • Infrastructure-as-code drift: the live state of Terraform, CloudFormation, Ansible, or similar tooling has diverged from the last reviewed and applied configuration.
  • Secrets, certificates, or feature flags were rotated in the secret manager or console but the consumers were not redeployed to pick up the new values.
  • Container base images, OS packages, or language runtimes were updated outside the release pipeline, changing the effective runtime without a corresponding contract update.
  • Environment-specific overlays (region, AZ, tenant, customer tier) were overridden by an emergency change that was never committed back to the source of truth.
  • CI/CD pipeline caches are pinning older artifacts, configuration files, or compiled outputs across successive deploys, so a newer build inherits stale configuration.
  • Load balancer, ingress, API gateway, or service mesh configuration was modified to remediate an unrelated incident and was not reflected in the deployment contract.

First ten minutes

  1. 01Capture the exact deployed artifact identifier - image digest, container SHA, or binary checksum - currently running in production, since mutable tags hide drift.
  2. 02Diff the live environment variables and mounted configuration files of the failing instance against the values declared in the reviewed manifest or IaC output.
  3. 03Compare one failing node or pod against a known-good peer on the same artifact to isolate which component - config, secret, network, or data - actually drifted.
  4. 04Pull recent change records: commits, IaC applies, console edits, secret rotations, and infra tickets touching this service or environment in the last 24 to 72 hours.
  5. 05Snapshot the failing process state - open files, listening ports, effective config paths, DNS resolvers - before any restart erases volatile evidence.
  6. 06Classify the drift candidate as code, config, data, or infrastructure before proposing any change, so the fix targets the actual layer that diverged.

Evidence to collect

  • Image digests, build SHAs, and package versions of every component currently running on the failing production target, taken directly from the live system.
  • Full effective configuration of the failing service: process environment, mounted volumes, resolved ConfigMaps and Secrets, and on-disk config files.
  • Recent commit, merge, IaC apply, and console change logs that touched the affected service, environment, region, or dependency within the investigation window.
  • End-to-end network trace from the failing service to its upstream and downstream dependencies, including DNS resolution and TLS handshake details.
  • Logs, metrics, and distributed traces from a representative failing request, with synchronized timestamps across the request path and infrastructure layers.
  • A field-by-field diff between the production state and the last reviewed deployment contract or golden configuration baseline.

Where to look

  • The deployment manifest, Helm chart values, Kustomize overlay, Terraform output, or Ansible inventory declared for this specific environment in source control.
  • The running container or VM filesystem, mounted ConfigMaps and Secrets, process environment, and any init or sidecar containers that inject configuration.
  • The CI/CD pipeline: artifact cache, container registry tags and digests, promotion records, and the exact build context that produced the running image.
  • Secret manager, KMS, certificate authority, and vault audit logs for rotations, revocations, or policy changes in the affected time window.
  • Load balancer, ingress controller, API gateway, service mesh control plane, and DNS records that sit between clients and the affected service.
  • IaC state files, drift detection reports, and plan output that compares declared state against the actual state of the production target.

Diagnostic steps

  1. 01Pin the running artifact by immutable identifier - container image digest or binary SHA - never trust mutable tags like latest or stable when comparing versions.
  2. 02Export the effective configuration from the running process and diff it field by field against the reviewed manifest, recording both the expected and observed values.
  3. 03Run a declarative drift detection tool against the production target so any undeclared manual change is surfaced before further investigation proceeds.
  4. 04Reproduce the failure in an isolated environment seeded with the captured production configuration to confirm that configuration alone, not code, drives the divergence.
  5. 05Diff runtime dependencies - OS packages, language runtimes, native libraries - between the staging and production build contexts to catch undeclared version drift.
  6. 06Trace a representative failing request edge to backend and identify the first hop where behavior diverges from the expected contract.
  7. 07Verify the contract: confirm that the production target still matches the IaC declared state, and if not, identify the manual override that caused the drift.

Common mistakes

  • Trusting mutable tags such as latest or stable instead of pinning to immutable digests, so the version comparison hides the actual drift.
  • Restarting the failing service to "fix" the drift before capturing volatile state, which destroys the evidence needed to identify the root cause.
  • Editing production configuration to match staging without first understanding why staging was intentionally different, propagating the wrong baseline.
  • Treating an environment-only failure as a code defect and rolling back code, when the contract itself - not the build - has drifted.
  • Reading secret values or credentials into shared logs, chat threads, or tickets during triage, which leaks them beyond their intended consumers.
  • Conflating application feature flags with infrastructure-level configuration in the same investigation, so changes are applied to the wrong layer.

Safe fixes

  • Reapply the reviewed configuration from the IaC source rather than patching the running instance by hand, so the correction is auditable and reproducible.
  • Promote the same immutable artifact, pinned by digest, that passed in staging into production unchanged, rather than rebuilding or retagging.
  • Roll any manually edited production values back through the change management pipeline, preserving an audit trail of who changed what and why.
  • Validate the corrected configuration against a canary or shadow subset of production traffic before widening the rollout to the full fleet.
  • Rotate secrets in the secret manager and let consumers reload, rather than embedding values in the artifact or the deployment manifest.

Prove the fix

  1. 01Declarative drift detection reports zero undeclared changes between the IaC declared state and the live state of the production target.
  2. 02The effective configuration of the running service matches the reviewed deployment contract field by field, with no observed-versus-expected deltas.
  3. 03The representative failing scenario succeeds end-to-end against the production target, verified by a fresh distributed trace and matching logs.
  4. 04Staging and production effective configuration diffs are empty for every field that was previously drifted, confirming parity between environments.
  5. 05Synthetic probes and canary traffic continue to pass for a full soak window after the fix is applied, with no reintroduction of drift in that interval.

Prevention and next steps

  • Adopt strict 12-factor configuration discipline: keep all environment-specific values in environment variables and external stores, never in code or images.
  • Pin every deployment artifact to an immutable digest in the deployment contract, and reject mutable tags anywhere in the production promotion path.
  • Run continuous declarative drift detection against every environment and alert on any undeclared change so divergence is caught before it causes incidents.
  • Require every production change - including secrets, feature flags, and infrastructure - to flow through reviewed IaC and CI/CD with an audit trail.
  • Separate build, release, and run stages so the exact same build artifact is promoted unchanged from staging through production, eliminating build-time drift.

Safe commands and checks

kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.status.containerStatuses[*].imageID}' -- capture the immutable image digest currently running in production
kubectl exec -n <namespace> <pod-name> -- env | sort -- dump the effective process environment of the running container without modifying it
kubectl get configmap <configmap-name> -n <namespace> -o yaml -- read the live ConfigMap contents applied to the failing workload
kubectl diff -f <reviewed-manifest-file> -- diff the reviewed manifest against the cluster's live state to surface undeclared changes
terraform plan -detailed-exitcode -- detect drift between declared and actual infrastructure state without applying any change
docker image inspect <image:tag-or-digest> --format '{{.Id}} {{.Config.Env}}' -- inspect image metadata, including any embedded environment defaults
kubectl logs -n <namespace> <pod-name> --previous --timestamps -- capture logs from the prior container instance without restarting the workload
kubectl get events -n <namespace> --sort-by='.lastTimestamp' -- list recent cluster events related to scheduling, mounting, and configuration in the namespace