Kubernetes Secrets
A complete guide to Kubernetes Secrets — what they are, why they exist, how they work internally, where they are used, and how to troubleshoot real-world secret issues with hands-on examples.
📋 Table of Contents
- What is a Kubernetes Secret?
- Why Do We Need Secrets?
- How Kubernetes Secrets Work Internally
- Types of Kubernetes Secrets
- Creating Secrets
- Using Secrets in Pods
- Where Secrets Are Used in Real Projects
- Secret vs ConfigMap — When to Use What
- Security Best Practices
- Troubleshooting Secrets — Real-World Examples
- Summary & Quick Reference
1. What is a Kubernetes Secret?
A Kubernetes Secret is a Kubernetes object designed to store and manage sensitive information — such as passwords, API tokens, SSH keys, TLS certificates, and database credentials — separately from your application code and container images.
In Kubernetes, everything is an “object” (Pod, Deployment, Service, etc.). A Secret is just another object — but it is treated specially by the cluster:
- It is stored in etcd (Kubernetes’ key-value store) with optional encryption at rest
- It is only sent to the nodes that need it
- It is held in tmpfs (memory), never written to disk on the node
- Access to it is controlled by RBAC (Role-Based Access Control)
📌 A Simple Definition
A Secret is a Kubernetes-native way to store small pieces of sensitive data and inject them into Pods at runtime, without hardcoding them anywhere in your source code.
🔑 What Can You Store in a Secret?
| Sensitive Data | Example |
|---|---|
| Database password | postgres_password=Sup3rS3cr3t! |
| API keys | STRIPE_API_KEY=sk_live_abc123... |
| OAuth tokens | GITHUB_TOKEN=ghp_... |
| TLS/SSL certificates | .crt and .key files |
| SSH private keys | id_rsa |
| Docker registry credentials | Username + password for pulling private images |
| JWT signing keys | JWT_SECRET=myHmacKey |
📸 [IMAGE SUGGESTION] A diagram showing a Secret object in the center, with arrows pointing to: Pod (as env var), Pod (as mounted file), and etcd (storage). Label the flow clearly. Use a lock icon on the Secret object.
2. Why Do We Need Secrets?
The Problem: Hardcoded Credentials
Imagine you have a Node.js app that connects to a PostgreSQL database:
// ❌ BAD — NEVER DO THIS
const client = new Client({
host: 'postgres.example.com',
user: 'admin',
password: 'SuperSecret123!', // Hardcoded in source code!
database: 'myapp'
});
What happens if you commit this?
Git history is permanent.
Someone forks the repo.
A contractor gets access.
GitHub scans find it.
An attacker gains database access.
Even if you delete the line and push again, the password is still visible in git history.
The Naive Fix: Environment Variables in Docker
# ❌ Still bad — the password is visible in docker inspect
ENV DB_PASSWORD=SuperSecret123!
Or in a Kubernetes manifest:
# ❌ Still bad — password in plain text in your YAML file (which lives in Git!)
env:
- name: DB_PASSWORD
value: "SuperSecret123!"
The Kubernetes Way: Secrets
# ✅ GOOD — The actual password lives in a Secret object, not your app YAML
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials # name of the Secret object
key: password # key inside the Secret
Now your application YAML is safe to commit. The actual sensitive value lives in the Secret object, which is:
- Stored separately in etcd
- Access-controlled via RBAC
- Never visible in your source code or Git
Why Not Just Use ConfigMaps?
ConfigMaps also store key-value data — but they store it in plain text. Anyone with read access to your cluster can read a ConfigMap. Secrets have:
- Base64 encoding (obfuscation, not encryption — but signals intent)
- Restricted RBAC defaults in well-configured clusters
- Encryption at rest support via EncryptionConfiguration
- Audit log tracking of who accessed them
📸 [IMAGE SUGGESTION] A side-by-side comparison: Left shows “Hardcoded → Git → Breach” risk chain. Right shows “Secret object → etcd → Pod (runtime only)” safe chain. Use red/green color coding.
3. How Kubernetes Secrets Work Internally
Understanding the internals helps you debug and secure secrets properly.
3.1 Storage in etcd
When you create a Secret, Kubernetes stores it in etcd — the cluster’s distributed key-value database.
kubectl create secret → API Server → etcd
↓
Stored as base64-encoded data
(optionally encrypted at rest)
By default, secrets are stored in etcd as base64-encoded values, which is not encryption. Anyone with direct etcd access can decode them. For true encryption, you must configure EncryptionConfiguration with a provider like aescbc, aesgcm, or a KMS provider.
3.2 How a Pod Gets a Secret
1. You create a Pod that references a Secret
↓
2. API Server validates the Pod spec
↓
3. Scheduler assigns the Pod to a Node
↓
4. kubelet on that Node fetches the Secret from the API Server
↓
5. kubelet injects the Secret into the Pod as:
- Environment variable (copy into process env)
- Volume mount (tmpfs — memory filesystem, not on disk)
↓
6. Container starts and reads the value
3.3 tmpfs — Why It Matters
When Secrets are mounted as volumes, Kubernetes mounts them on tmpfs — a temporary filesystem that lives in RAM, not on the node’s disk. This means:
- Secrets are never written to disk storage on the node
- They disappear when the node reboots
- A disk forensic analysis cannot recover them
Pod
├── Container
│ └── /etc/secrets/db-password ← tmpfs (in memory)
│ (NOT written to /var/lib/kubelet or any disk path)
3.4 Base64 Encoding
Secret values are stored as base64-encoded strings. This is not encryption — it’s just encoding to safely store binary data (like certificates) as text.
# Encoding
echo -n "MyPassword123" | base64
# Output: TXlQYXNzd29yZDEyMw==
# Decoding
echo "TXlQYXNzd29yZDEyMw==" | base64 -d
# Output: MyPassword123
⚠️ Important: Base64 is reversible by anyone. The security of a Secret comes from RBAC access control and etcd encryption, NOT from base64 encoding.
3.5 The Secret Lifecycle
Create Secret
│
▼
Stored in etcd (base64 / encrypted)
│
▼
Pod references Secret
│
▼
kubelet fetches Secret → injects into Pod
│
├── As ENV VAR: copied into process environment at container start
│ (static — doesn't update if Secret changes)
│
└── As Volume: mounted as files on tmpfs
(can update automatically — kubelet syncs changes)
│
▼
Pod terminates → Secret removed from node memory
📸 [IMAGE SUGGESTION] A flow diagram showing the full lifecycle: API Server → etcd → kubelet → Pod → tmpfs volume / env var. Label each component. Add a lock icon at etcd. Show the “base64 encode/decode” step clearly.
4. Types of Kubernetes Secrets
Kubernetes has several built-in Secret types. Each type tells Kubernetes (and your tools) how to interpret and use the data.
4.1 Opaque (Default)
The most common type. Arbitrary key-value pairs for any custom secret data.
apiVersion: v1
kind: Secret
metadata:
name: my-app-secrets
type: Opaque # ← default type
data:
db-password: U3VwZXJTZWNyZXQhMjM= # base64 encoded
api-key: c2tfdGVzdF9hYmMxMjM=
4.2 kubernetes.io/service-account-token
Automatically created by Kubernetes for each ServiceAccount. Contains a JWT token used by Pods to authenticate with the API Server.
# Every namespace has a default service account with this token
kubectl get secrets -n default
# NAME TYPE DATA
# default-token-abc12 kubernetes.io/service-account-token 3
4.3 kubernetes.io/dockerconfigjson
Stores Docker registry credentials for pulling private container images.
kubectl create secret docker-registry my-registry-secret \
--docker-server=registry.example.com \
--docker-username=myuser \
--docker-password=mypassword \
--docker-email=me@example.com
Used in Pod specs as imagePullSecrets:
spec:
imagePullSecrets:
- name: my-registry-secret
containers:
- name: my-app
image: registry.example.com/myapp:latest
4.4 kubernetes.io/tls
Stores TLS certificates and keys for HTTPS/SSL termination (typically used with Ingress controllers).
kubectl create secret tls my-tls-secret \
--cert=path/to/tls.crt \
--key=path/to/tls.key
# Used in Ingress
spec:
tls:
- hosts:
- myapp.example.com
secretName: my-tls-secret
4.5 kubernetes.io/basic-auth
For storing username and password for Basic Authentication.
apiVersion: v1
kind: Secret
type: kubernetes.io/basic-auth
data:
username: YWRtaW4= # admin
password: cGFzc3dvcmQ= # password
4.6 kubernetes.io/ssh-auth
Stores SSH private keys.
apiVersion: v1
kind: Secret
type: kubernetes.io/ssh-auth
data:
ssh-privatekey: <base64 encoded private key>
Secret Types Summary
| Type | Use Case |
|---|---|
Opaque | Generic — passwords, API keys, tokens |
kubernetes.io/service-account-token | Pod-to-API-Server auth (auto-managed) |
kubernetes.io/dockerconfigjson | Private Docker registry credentials |
kubernetes.io/tls | TLS certificates for Ingress |
kubernetes.io/basic-auth | HTTP basic auth credentials |
kubernetes.io/ssh-auth | SSH private key |
📸 [IMAGE SUGGESTION] A categorized card layout showing all 6 Secret types with an icon for each (lock, robot, docker whale, SSL shield, user, SSH terminal). Clean grid layout.
5. Creating Secrets
There are three ways to create Secrets: imperative (kubectl), declarative (YAML), and from files.
5.1 Method 1: Imperative — kubectl create secret
The fastest way. Kubernetes handles base64 encoding automatically.
# Create a generic (Opaque) secret with literal values
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password='Sup3rS3cr3t!'
# Verify it was created
kubectl get secret db-credentials
# NAME TYPE DATA AGE
# db-credentials Opaque 2 5s
# View the secret (values are base64 encoded)
kubectl get secret db-credentials -o yaml
Output:
apiVersion: v1
data:
password: U3VwM3JTM2NyM3Qh
username: YWRtaW4=
kind: Secret
metadata:
name: db-credentials
namespace: default
type: Opaque
5.2 Method 2: From Files
Useful when the secret is already a file (certificate, SSH key, .env file).
# Create secret from a file
echo -n "Sup3rS3cr3t!" > ./db_password.txt
kubectl create secret generic db-credentials \
--from-file=password=./db_password.txt
# Create from multiple files
kubectl create secret generic app-certs \
--from-file=tls.crt=./server.crt \
--from-file=tls.key=./server.key
5.3 Method 3: Declarative — YAML Manifest
When you need the Secret as code (e.g., stored in a GitOps repo with Sealed Secrets or SOPS encryption).
With data (base64 encoded values):
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
data:
username: YWRtaW4= # echo -n "admin" | base64
password: U3VwM3JTM2NyM3Qh # echo -n "Sup3rS3cr3t!" | base64
With stringData (plain text — Kubernetes encodes it automatically):
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
stringData: # ← Use stringData for human-readable YAML
username: admin
password: Sup3rS3cr3t!
💡 Tip: Use
stringDatain your YAML files — it’s more readable and Kubernetes converts it to base64 automatically. Onkubectl get, values always appear as base64 in thedatafield regardless.
5.4 Decode a Secret Value
# Get a specific secret value and decode it
kubectl get secret db-credentials \
-o jsonpath='{.data.password}' | base64 -d
# Output: Sup3rS3cr3t!
6. Using Secrets in Pods
There are two primary ways to consume Secrets in your Pods.
6.1 Method 1: Environment Variables
Inject Secret values directly into the container’s process environment.
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod
spec:
containers:
- name: my-app
image: my-app:1.0
env:
# Inject a single key from a Secret
- name: DB_PASSWORD # env var name in the container
valueFrom:
secretKeyRef:
name: db-credentials # Secret object name
key: password # key inside the Secret
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
# Or inject ALL keys from a Secret as env vars at once
envFrom:
- secretRef:
name: db-credentials # All keys become env vars
Your app then reads them normally:
import os
db_password = os.environ.get('DB_PASSWORD')
const dbPassword = process.env.DB_PASSWORD;
⚠️ Limitation of env var approach:
- If the Secret changes, the Pod must be restarted to pick up the new value
- The secret value is visible in
kubectl describe pod(in process environment listings) - Environment variables can be leaked via verbose logging
6.2 Method 2: Volume Mounts (Recommended)
Mount the Secret as files in the container filesystem (stored in tmpfs — in memory).
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod
spec:
volumes:
# Declare the secret as a volume
- name: secret-volume
secret:
secretName: db-credentials # Secret object name
containers:
- name: my-app
image: my-app:1.0
volumeMounts:
# Mount the volume into the container filesystem
- name: secret-volume
mountPath: /etc/secrets # Directory in the container
readOnly: true # Always mount secrets as readOnly
Inside the container, each key in the Secret becomes a file:
/etc/secrets/
├── username ← contains "admin"
└── password ← contains "Sup3rS3cr3t!"
Your app reads it like any file:
with open('/etc/secrets/password', 'r') as f:
db_password = f.read().strip()
const fs = require('fs');
const dbPassword = fs.readFileSync('/etc/secrets/password', 'utf8').trim();
✅ Advantages of Volume Mount approach:
- Secrets auto-update in the container when changed (kubelet syncs within ~1 minute) — no restart needed
- Stored in tmpfs — not on disk
- Can control file permissions with
defaultMode - Specific keys can be projected to specific file paths
6.3 Mount Specific Keys with Custom File Names
volumes:
- name: secret-volume
secret:
secretName: db-credentials
items:
- key: password
path: db/password.txt # mounts as /etc/secrets/db/password.txt
mode: 0400 # read-only for owner only
- key: username
path: db/username.txt
6.4 Using Secrets for Image Pull
apiVersion: v1
kind: Pod
spec:
imagePullSecrets:
- name: my-registry-secret # Secret of type kubernetes.io/dockerconfigjson
containers:
- name: private-app
image: private-registry.example.com/my-app:latest
📸 [IMAGE SUGGESTION] A Pod diagram with two inset boxes showing the two injection methods: (1) Arrow from Secret → env var (labeled “DB_PASSWORD=…”), (2) Arrow from Secret → volume mount (labeled “/etc/secrets/password”). Show tmpfs label on the volume mount path.
7. Where Secrets Are Used in Real Projects
7.1 Database Connections
# Secret
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
stringData:
DB_HOST: postgres-service
DB_PORT: "5432"
DB_NAME: myapp
DB_USER: appuser
DB_PASSWORD: Secr3tP@ss!
---
# Deployment using the secret
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-api
spec:
template:
spec:
containers:
- name: api
image: my-backend:v1.2
envFrom:
- secretRef:
name: postgres-secret
7.2 Third-Party API Keys (Stripe, Twilio, SendGrid)
apiVersion: v1
kind: Secret
metadata:
name: external-api-keys
stringData:
STRIPE_SECRET_KEY: sk_live_abc123xyz
SENDGRID_API_KEY: SG.abc123...
TWILIO_AUTH_TOKEN: abc123...
7.3 TLS Termination with Ingress
# Create TLS secret
kubectl create secret tls myapp-tls \
--cert=certs/tls.crt \
--key=certs/tls.key
# Use in Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
spec:
tls:
- hosts:
- myapp.example.com
secretName: myapp-tls # ← TLS Secret
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-service
port:
number: 80
7.4 Private Docker Registry (Self-hosted or GCR/ECR)
# For AWS ECR
kubectl create secret docker-registry ecr-secret \
--docker-server=123456789.dkr.ecr.us-east-1.amazonaws.com \
--docker-username=AWS \
--docker-password=$(aws ecr get-login-password --region us-east-1)
7.5 Git Repository Credentials (in CI/CD)
apiVersion: v1
kind: Secret
metadata:
name: git-credentials
type: kubernetes.io/basic-auth
stringData:
username: git-bot-user
password: ghp_token_here
7.6 JWT / Session Signing Keys
apiVersion: v1
kind: Secret
metadata:
name: jwt-secret
stringData:
JWT_SECRET: "a-very-long-and-random-string-used-for-hmac-signing"
SESSION_SECRET: "another-random-secret-for-express-sessions"
8. Secret vs ConfigMap — When to Use What
This is one of the most common questions in Kubernetes. The rule is simple:
| Criterion | Secret | ConfigMap |
|---|---|---|
| Sensitive data (passwords, tokens, keys) | ✅ Use Secret | ❌ Never |
| Non-sensitive config (URLs, feature flags, log levels) | ❌ Overkill | ✅ Use ConfigMap |
| Base64 encoded in etcd | Yes | No (plain text) |
| Encryption at rest support | Yes | No |
| RBAC typically stricter | Yes | Less so |
Visible in kubectl describe | Partially hidden | Fully visible |
Decision Rule
Ask: "Would I be embarrassed if this value appeared in a log file or
was committed to a public GitHub repo?"
YES → Use a Secret
NO → Use a ConfigMap
Examples
# ✅ ConfigMap — non-sensitive configuration
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
MAX_CONNECTIONS: "100"
APP_ENV: "production"
ALLOWED_ORIGINS: "https://myapp.com,https://api.myapp.com"
---
# ✅ Secret — sensitive credentials
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
stringData:
DB_PASSWORD: "Secr3tP@ssw0rd"
JWT_SECRET: "hmac-signing-key-abc123"
REDIS_PASSWORD: "redis-pass-xyz"
📸 [IMAGE SUGGESTION] A two-column comparison table rendered as a visual card: ConfigMap (blue) on the left with non-sensitive examples, Secret (gold/yellow with lock icon) on the right with sensitive examples. Use a decision flowchart below it.
9. Security Best Practices
Kubernetes Secrets are only as secure as you make them. Here are production-grade practices.
9.1 Enable Encryption at Rest
By default, Secrets in etcd are only base64-encoded. Enable true encryption:
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-32-byte-key>
- identity: {} # fallback — allows reading unencrypted (during migration)
Pass to kube-apiserver:
--encryption-provider-config=/etc/kubernetes/encryption-config.yaml
9.2 Use RBAC to Restrict Secret Access
# Only allow the backend service account to read specific secrets
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: backend-secret-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-credentials", "jwt-secret"] # specific secrets only
verbs: ["get"] # read-only — no list, no create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: backend-reads-secrets
namespace: production
subjects:
- kind: ServiceAccount
name: backend-sa
namespace: production
roleRef:
kind: Role
name: backend-secret-reader
apiGroup: rbac.authorization.k8s.io
9.3 Never Store Secrets in Git Plaintext
Instead, use tools like:
| Tool | Approach |
|---|---|
| Sealed Secrets (Bitnami) | Encrypt Secret before committing; only cluster can decrypt |
| SOPS (Mozilla) | Encrypt specific values in YAML with KMS/PGP |
| HashiCorp Vault | External secret management; dynamic secrets |
| External Secrets Operator | Sync secrets from AWS SSM, GCP Secret Manager, Vault |
| Doppler / Infisical | SaaS secret management platforms |
9.4 Rotate Secrets Regularly
Treat secrets like passwords — rotate them periodically:
# Update a secret value (patch in place)
kubectl patch secret db-credentials \
--type='json' \
-p='[{"op": "replace", "path": "/data/password", "value": "'$(echo -n "NewP@ssw0rd!" | base64)'"}]'
# Or recreate it
kubectl delete secret db-credentials
kubectl create secret generic db-credentials \
--from-literal=password='NewP@ssw0rd!'
After rotation, Pods using volume-mounted secrets will pick up the change automatically within ~1 minute. Pods using env vars must be restarted.
9.5 Use readOnly: true on Volume Mounts
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true # ← Always add this
9.6 Set Minimal File Permissions
volumes:
- name: secret-volume
secret:
secretName: db-credentials
defaultMode: 0400 # ← Owner read-only (octal)
9.7 Avoid Logging Secret Values
# ❌ BAD — logs the actual secret
print(f"Connecting with password: {os.environ.get('DB_PASSWORD')}")
# ✅ GOOD — only confirm it's set
print(f"DB password set: {'yes' if os.environ.get('DB_PASSWORD') else 'no'}")
10. Troubleshooting Secrets — Real-World Examples
This section covers the most common Secret-related issues you’ll encounter in real Kubernetes environments, with exact commands and fixes.
🔴 Problem 1: Pod is in CreateContainerConfigError — Secret Not Found
Symptom:
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# my-app-pod 0/1 CreateContainerConfigError 0 30s
Diagnosis:
kubectl describe pod my-app-pod
Output shows:
Events:
Warning Failed 5s kubelet Error: secret "db-credentials" not found
Root Cause: The Pod references a Secret that doesn’t exist in the same namespace.
Fix:
# Check which namespace the pod is in
kubectl get pod my-app-pod -o jsonpath='{.metadata.namespace}'
# Output: production
# Check if the secret exists in that namespace
kubectl get secret db-credentials -n production
# Error: secrets "db-credentials" not found
# The secret exists in 'default' but not 'production'!
kubectl get secret db-credentials -n default
# NAME TYPE DATA AGE
# db-credentials Opaque 2 1d
# Fix: Recreate the secret in the correct namespace
kubectl get secret db-credentials -n default -o yaml \
| sed 's/namespace: default/namespace: production/' \
| kubectl apply -f -
Prevention: Always specify the namespace when creating secrets:
kubectl create secret generic db-credentials \
--from-literal=password='Sup3rS3cr3t!' \
-n production # ← always specify namespace
🔴 Problem 2: Error: secret key "password" not found
Symptom: Pod starts but application crashes immediately.
Diagnosis:
kubectl logs my-app-pod
# Error: environment variable DB_PASSWORD is empty or not set
kubectl describe pod my-app-pod
# Events:
# Warning Failed kubelet Error: couldn't find key password in Secret default/db-credentials
Root Cause: The key name in the Pod spec doesn’t match the key name in the Secret.
# Check what keys actually exist in the Secret
kubectl get secret db-credentials -o jsonpath='{.data}' | python3 -m json.tool
# {
# "db_password": "U3VwM3JTM2NyM3Qh", ← underscore!
# "db_username": "YWRtaW4="
# }
Pod spec has:
secretKeyRef:
name: db-credentials
key: password # ← looking for "password" but Secret has "db_password"
Fix Option A: Update the Secret key name to match what Pod expects:
# Get current value
CURRENT_PASS=$(kubectl get secret db-credentials -o jsonpath='{.data.db_password}' | base64 -d)
# Recreate with correct key name
kubectl delete secret db-credentials
kubectl create secret generic db-credentials \
--from-literal=password="$CURRENT_PASS" \
--from-literal=username="admin"
Fix Option B: Update the Pod spec to use the correct key:
secretKeyRef:
name: db-credentials
key: db_password # ← match the actual key name
🔴 Problem 3: Application Gets Wrong / Corrupted Secret Value
Symptom: Authentication fails even though the secret seems correct.
Diagnosis:
# Check the raw base64 value
kubectl get secret db-credentials -o jsonpath='{.data.password}'
# U3VwM3JTM2NyM3Qh==
# Decode it
kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d
# Sup3rS3cr3t!% ← notice the % (trailing newline!) on some terminals
Root Cause: When creating secrets from files or with echo (without -n), a trailing newline gets encoded into the value.
# ❌ BAD — includes a newline character
echo "Sup3rS3cr3t!" | base64
# U3VwM3JTM2NyM3Qh Cg== ← "Cg==" is the base64 of "\n"
# ✅ GOOD — -n flag suppresses trailing newline
echo -n "Sup3rS3cr3t!" | base64
# U3VwM3JTM2NyM3Qh
Fix:
kubectl delete secret db-credentials
# Use --from-literal (kubectl handles encoding correctly)
kubectl create secret generic db-credentials \
--from-literal=password='Sup3rS3cr3t!'
Or if using a file:
# Strip trailing newline from file
printf '%s' 'Sup3rS3cr3t!' > ./password.txt # printf has no trailing newline
kubectl create secret generic db-credentials \
--from-file=password=./password.txt
🔴 Problem 4: Secret Changes Not Reflected in Running Pod
Symptom: You updated a Secret but the running application still uses the old value.
Diagnosis:
# You updated the secret
kubectl patch secret db-credentials \
--type='merge' \
-p '{"stringData": {"password": "NewP@ssw0rd2026!"}}'
# But the app still uses the old password
kubectl exec my-app-pod -- env | grep DB_PASSWORD
# DB_PASSWORD=OldP@ssw0rd! ← still old value!
Root Cause: This depends on how the Secret is injected:
| Injection Method | Auto-updates? | Restart Required? |
|---|---|---|
envFrom / env.valueFrom.secretKeyRef | ❌ No | ✅ Yes |
volumeMount (files) | ✅ Yes (~1 min) | ❌ No |
Environment variables are set once at container startup and never change during the container’s lifetime.
Fix for env vars — Rolling Restart:
# Restart all Pods in the Deployment to pick up new env var values
kubectl rollout restart deployment/my-app
# Watch the rollout
kubectl rollout status deployment/my-app
# Waiting for deployment "my-app" rollout to finish...
# deployment "my-app" successfully rolled out
Prevention — Use volume mounts for dynamic secrets:
# Instead of env vars, use volume mounts for secrets that rotate
volumeMounts:
- name: db-secret
mountPath: /etc/secrets
readOnly: true
volumes:
- name: db-secret
secret:
secretName: db-credentials
🔴 Problem 5: ImagePullBackOff — Private Registry Secret Issue
Symptom:
kubectl get pods
# NAME READY STATUS RESTARTS
# my-app 0/1 ImagePullBackOff 0
Diagnosis:
kubectl describe pod my-app
# Events:
# Warning Failed kubelet Failed to pull image "registry.example.com/my-app:latest":
# rpc error: unauthorized: authentication required
Root Cause: Missing or incorrect imagePullSecrets.
Fix:
# Step 1: Create the registry secret
kubectl create secret docker-registry registry-credentials \
--docker-server=registry.example.com \
--docker-username=myuser \
--docker-password=mypassword \
-n your-namespace
# Step 2: Verify the secret was created
kubectl get secret registry-credentials -o json | \
python3 -c "import sys,json,base64; \
d=json.load(sys.stdin); \
print(base64.b64decode(d['data']['.dockerconfigjson']).decode())"
# Should show: {"auths":{"registry.example.com":{"username":"myuser",...}}}
# Step 3: Add imagePullSecrets to your Pod/Deployment
kubectl patch deployment my-app --patch \
'{"spec":{"template":{"spec":{"imagePullSecrets":[{"name":"registry-credentials"}]}}}}'
# Step 4: Watch pods recover
kubectl get pods -w
Alternative — Attach imagePullSecrets to the ServiceAccount (so all Pods automatically get it):
kubectl patch serviceaccount default \
-p '{"imagePullSecrets": [{"name": "registry-credentials"}]}'
🔴 Problem 6: RBAC — Error from server (Forbidden): secrets is forbidden
Symptom: A developer or CI/CD pipeline cannot read a secret.
kubectl get secret db-credentials
# Error from server (Forbidden): secrets "db-credentials" is forbidden:
# User "ci-bot" cannot get resource "secrets" in API group ""
Diagnosis:
# Check what permissions ci-bot has
kubectl auth can-i get secrets --as=ci-bot -n production
# no
kubectl auth can-i list secrets --as=ci-bot -n production
# no
Fix:
# Create a Role that allows reading specific secrets
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ci-secret-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-credentials"] # restrict to only this secret
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ci-reads-db-secret
namespace: production
subjects:
- kind: User
name: ci-bot
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: ci-secret-reader
apiGroup: rbac.authorization.k8s.io
kubectl apply -f ci-secret-rbac.yaml
# Verify
kubectl auth can-i get secrets/db-credentials --as=ci-bot -n production
# yes
🔴 Problem 7: Volume Mount — Permission Denied
Symptom: Container starts but cannot read secret files.
kubectl logs my-app-pod
# Error: EACCES: permission denied, open '/etc/secrets/password'
Diagnosis:
kubectl exec my-app-pod -- ls -la /etc/secrets/
# -r-------- 1 root root 13 Jan 20 10:00 password ← only root can read!
Root Cause: The secret file permissions (defaultMode) are too restrictive, and the container runs as a non-root user.
Fix Option A: Set permissive defaultMode on the secret volume:
volumes:
- name: secret-volume
secret:
secretName: db-credentials
defaultMode: 0444 # ← world-readable (for non-sensitive but restricted info)
# OR
defaultMode: 0440 # ← owner + group readable
Fix Option B: Set fsGroup in Pod’s security context so the volume is group-accessible:
spec:
securityContext:
fsGroup: 1000 # ← files will be group-owned by GID 1000
containers:
- name: my-app
securityContext:
runAsUser: 1000 # ← container runs as UID 1000
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
🛠️ Troubleshooting Cheat Sheet
# 1. Check if Secret exists in correct namespace
kubectl get secrets -n <namespace>
# 2. Check Secret keys and their (base64) values
kubectl get secret <name> -o yaml -n <namespace>
# 3. Decode a specific key
kubectl get secret <name> -n <namespace> \
-o jsonpath='{.data.<key>}' | base64 -d
# 4. Check Pod events for Secret-related errors
kubectl describe pod <pod-name> | grep -A 20 "Events:"
# 5. Verify env vars inside a running container
kubectl exec <pod-name> -- env | grep <VAR_NAME>
# 6. Check if Secret is mounted correctly
kubectl exec <pod-name> -- ls -la /etc/secrets/
kubectl exec <pod-name> -- cat /etc/secrets/password
# 7. Check RBAC permissions
kubectl auth can-i get secrets -n <namespace>
kubectl auth can-i get secrets --as=<user> -n <namespace>
# 8. Describe the secret (shows metadata, not values)
kubectl describe secret <name> -n <namespace>
# 9. Force restart deployment after secret rotation
kubectl rollout restart deployment/<name> -n <namespace>
# 10. Check etcd encryption (admin only)
kubectl get secret <name> -n <namespace> -o json | \
python3 -c "import sys,json,base64; \
d=json.load(sys.stdin); \
[print(k,'=', base64.b64decode(v).decode()) \
for k,v in d['data'].items()]"
📸 [IMAGE SUGGESTION] A troubleshooting decision tree flowchart:
- Start: “Pod has Secret issue”
- Branch 1:
CreateContainerConfigError→ “Secret not found” → check namespace- Branch 2: App crashes → “Key not found” → check key names
- Branch 3: Wrong value → “Trailing newline?” → recreate with –from-literal
- Branch 4: Old value after update → “Env var?” → restart; “Volume?” → auto-updates Use color-coded boxes (red for errors, green for fixes).
11. Summary & Quick Reference
Core Concepts
| Concept | Description |
|---|---|
| What | A K8s object to store sensitive data (passwords, tokens, certs) |
| Why | Separate sensitive config from code; enforce access control |
| Storage | Stored in etcd, base64-encoded (optionally encrypted at rest) |
| Delivery | Injected into Pods as env vars or volume-mounted files |
| Security | Protected by RBAC; use EncryptionConfiguration for at-rest encryption |
Quick Command Reference
# Create
kubectl create secret generic <name> --from-literal=key=value
kubectl create secret generic <name> --from-file=key=./file.txt
kubectl create secret tls <name> --cert=tls.crt --key=tls.key
kubectl create secret docker-registry <name> --docker-server=... --docker-username=... --docker-password=...
# Read
kubectl get secrets
kubectl get secret <name> -o yaml
kubectl get secret <name> -o jsonpath='{.data.<key>}' | base64 -d
# Update
kubectl patch secret <name> --type='merge' -p '{"stringData":{"key":"newvalue"}}'
# Delete
kubectl delete secret <name>
# Restart Pods to pick up env var changes
kubectl rollout restart deployment/<name>
Secret Injection Methods
Method 1: Environment Variable
✅ Simple to use
❌ No auto-update on secret change (requires pod restart)
❌ Visible via kubectl describe
Method 2: Volume Mount (Recommended for production)
✅ Auto-updates within ~1 minute of secret change
✅ Stored in tmpfs (memory only, not disk)
✅ Granular file permissions
❌ Slightly more YAML to write
Common Errors at a Glance
| Error | Cause | Fix |
|---|---|---|
secret "x" not found | Wrong namespace | Recreate secret in correct namespace |
key "x" not found | Key name mismatch | Check actual keys with kubectl get secret -o yaml |
| Corrupted value / auth fails | Trailing newline in value | Use echo -n or --from-literal |
| Old value after rotation | Env var (static) | Restart Pods with kubectl rollout restart |
ImagePullBackOff | Missing imagePullSecrets | Create docker-registry secret and add to Pod spec |
Forbidden: secrets | RBAC not configured | Create Role + RoleBinding |
Permission denied on mount | defaultMode too restrictive | Set defaultMode: 0444 or configure fsGroup |
📸 [IMAGE SUGGESTION — Summary] A single-page “Kubernetes Secrets Cheat Sheet” infographic with four quadrants: (1) Creation commands, (2) Injection methods, (3) Common errors + fixes, (4) Best practices checklist. This makes a great printable reference card.
Part of the Kubernetes Configuration & Security module. Next: Kubernetes ConfigMaps | Previous: Kubernetes Namespaces