How to Fix CreateContainerConfigError: Secret Not Found in Kubernetes
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 10 mins
TL;DR
- What broke: Kubernetes cannot start your container because the pod spec references a
Secretobject that does not exist in the pod's namespace — the kubelet fails at config materialization before the container ever runs. - How to fix it: Either create the missing Secret (
kubectl create secret) or correct the secret name/namespace reference in your pod spec. - Use our Client-Side Sandbox above to paste your failing pod YAML and auto-diagnose the broken
secretKeyRef,secretRef, orvolumes.secretreference.
The Incident (What Does the Error Mean?)
Raw event output from kubectl describe pod <pod-name>:
Warning Failed 3s kubelet Error: CreateContainerConfigError
Message: secret "db-credentials" not found
The kubelet resolves all Secret and ConfigMap references before calling the container runtime. If any referenced Secret is absent, the pod is stuck in Pending with CreateContainerConfigError — it never reaches ContainerCreating. Every replica in the Deployment is dead. Your service is down.
The Attack Vector / Blast Radius
This is not just a misconfiguration annoyance. The blast radius:
- Full deployment outage. Every pod in the ReplicaSet fails identically. No rollback helps unless the previous revision also had the secret — and if you just rotated it, it probably didn't.
- Silent CI/CD gap. Most pipelines
kubectl applythe Deployment manifest but never verify that dependent Secrets were applied first. A secret deleted by a namespace cleanup job, a Helm rollback, or a Vault sync failure silently kills prod on the next pod reschedule (node drain, HPA scale-up, spot termination). - Secret sprawl risk. The reactive fix —
kubectl create secret genericwith a hardcoded value pasted from Slack — is how plaintext credentials end up in shell history, CI logs, and incident tickets. This is a credential hygiene incident waiting to happen. - Namespace isolation failure. Secrets are namespace-scoped. A pod in
namespace: productioncannot read a secret innamespace: staging. Cross-namespace secret references silently fail here, and engineers waste 20 minutes before checking the namespace.
How to Fix It (The Solution)
Basic Fix — Create the Missing Secret
First, confirm exactly what's missing:
# Check what the pod expects
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].env[*].valueFrom.secretKeyRef}'
# Check what exists in the namespace
kubectl get secrets -n <namespace>
Create the secret if it's genuinely absent:
kubectl create secret generic db-credentials \
--from-literal=DB_PASSWORD='your-password' \
--from-literal=DB_USER='your-user' \
-n production
Enterprise Best Practice — Declarative Secret Management with Correct Reference Hygiene
The pod spec was likely referencing a wrong name or the secret was never provisioned by the pipeline. Fix both the reference and the provisioning:
# pod-spec.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: production
spec:
template:
spec:
containers:
- name: api
image: myapp:1.4.2
env:
- - name: DB_PASSWORD
- valueFrom:
- secretKeyRef:
- name: database-creds # secret didn't exist
- key: password
+ - name: DB_PASSWORD
+ valueFrom:
+ secretKeyRef:
+ name: db-credentials # matches the actual Secret object name
+ key: DB_PASSWORD
+ optional: false # explicit — fail fast, don't silently inject empty string
For Vault or AWS Secrets Manager backed secrets, use the External Secrets Operator so the ExternalSecret CR creates the native Kubernetes Secret before any pod references it:
-# Manual kubectl create secret (breaks in GitOps, leaks to shell history)
-kubectl create secret generic db-credentials --from-literal=DB_PASSWORD=$PASS
+# ExternalSecret CR — declarative, auditable, rotation-aware
+apiVersion: external-secrets.io/v1beta1
+kind: ExternalSecret
+metadata:
+ name: db-credentials
+ namespace: production
+spec:
+ refreshInterval: 1h
+ secretStoreRef:
+ name: vault-backend
+ kind: ClusterSecretStore
+ target:
+ name: db-credentials # this is what the pod references
+ creationPolicy: Owner
+ data:
+ - secretKey: DB_PASSWORD
+ remoteRef:
+ key: secret/production/database
+ property: password
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. Helm / Kustomize pre-flight validation
Add a helm lint + kubectl apply --dry-run=server step. Server-side dry-run hits the API server and will reject a pod spec referencing a non-existent secret — catching this in the pipeline, not in prod.
# In your GitHub Actions / GitLab CI step
kubectl apply --dry-run=server -f k8s/deployment.yaml
2. OPA/Gatekeeper policy — enforce secret pre-existence
# ConstraintTemplate: require referenced secrets to exist before pod admission
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
ref := container.env[_].valueFrom.secretKeyRef.name
not data.inventory.namespace[input.review.object.metadata.namespace]["v1"]["Secret"][ref]
msg := sprintf("Secret '%v' referenced in pod does not exist in namespace '%v'", [ref, input.review.object.metadata.namespace])
}
3. Checkov static scan on manifests
checkov -f k8s/deployment.yaml --check CKV_K8S_35
CKV_K8S_35 flags pods that do not use secrets as files or env vars from a verified source — catches dangling references in static analysis before kubectl apply.
4. Deployment ordering in GitOps (ArgoCD / Flux)
Use ArgoCD syncWave annotations to guarantee Secrets (or ExternalSecrets) are applied and healthy before Deployments:
metadata:
annotations:
argocd.argoproj.io/sync-wave: "-1" # Secrets wave runs first
Deployments default to wave 0 — they will not start until wave -1 resources are Healthy.