Secrets Management
Kubernetes Secrets are API objects for delivering sensitive values to Pods, not a complete secrets-management system by themselves. Secret values are base64-encoded, not encrypted by default in manifests. Protect Secrets with least-privilege RBAC, encryption at rest, external secret stores, Git-safe workflows, careful logging, and rotation procedures.
Mental Model
A Secret is a namespaced Kubernetes object that stores key-value data. Workloads consume it through environment variables, mounted files, image pull credentials, or controllers such as cert-manager and External Secrets Operator. The API server stores the object, etcd persists it, and the kubelet materializes it onto nodes for Pods that reference it.
The operational question is not “is this base64?” The real question is: who can read it, where is it stored, how does it rotate, and can it leak through Git, logs, tickets, shell history, dashboards, or overly broad RBAC?
Secret data moves from source to Kubernetes Secret to workload consumption.
Secret Types
| Type | Use | Watchpoint |
|---|---|---|
Opaque | Generic app credentials and key-value data. | Most common; schema is up to you. |
kubernetes.io/tls | TLS cert/key pair in tls.crt and tls.key. | Certificate lifecycle belongs on Certificate Management page. |
kubernetes.io/dockerconfigjson | Private registry pull credentials. | Used by imagePullSecrets. |
kubernetes.io/service-account-token | Legacy ServiceAccount token Secret. | Prefer projected short-lived tokens where possible. |
bootstrap.kubernetes.io/token | Cluster bootstrap tokens. | Highly sensitive cluster join material. |
Native Secret From Scratch
Use stringData when authoring a Secret manifest manually because it accepts plain strings and the API server writes base64-encoded data. Do not commit real plaintext credentials to Git.
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: app
type: Opaque
stringData:
username: app_user
password: change-me-in-real-secret-storekubectl create secret generic db-credentials -n app \
--from-literal=username=app_user \
--from-literal=password='REPLACE_ME'
kubectl get secret db-credentials -n app -o yaml
kubectl describe secret db-credentials -n app
# Decode only when necessary, and avoid pasting output into tickets or chat.
kubectl get secret db-credentials -n app -o jsonpath='{.data.password}' | base64 -d; echoConsume Secrets
Prefer mounted files when the app can read configuration from files, especially for values that may rotate. Environment variables are simple but only update when the container starts.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-api
namespace: app
spec:
replicas: 2
selector:
matchLabels:
app: web-api
template:
metadata:
labels:
app: web-api
spec:
containers:
- name: api
image: registry.example.com/web-api:1.2.3
volumeMounts:
- name: db-credentials
mountPath: /run/secrets/db
readOnly: true
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
volumes:
- name: db-credentials
secret:
secretName: db-credentials
items:
- key: password
path: passwordRotation Behavior
| Consumption Method | Rotation Behavior | Operational Action |
|---|---|---|
| Secret volume | Kubelet updates mounted files eventually. | App must reread file or be restarted. |
| Environment variable | Does not update inside a running container. | Rollout restart required. |
| External Secrets Operator synced Secret | Controller refreshes Kubernetes Secret on interval. | Still restart app if it reads only at startup. |
| Secrets Store CSI mount | Provider/driver dependent rotation. | Confirm sync and app reread behavior. |
kubectl create secret generic db-credentials -n app \
--from-literal=username=app_user \
--from-literal=password='NEW_VALUE' \
--dry-run=client -o yaml | kubectl apply -f -
kubectl rollout restart deploy/web-api -n app
kubectl rollout status deploy/web-api -n appExternal Secret Stores
In production, Kubernetes often should not be the original source of secret material. External Secrets Operator, Secrets Store CSI Driver, Vault Agent Injector, SOPS, Sealed Secrets, or cloud-native integrations let you keep source secrets in a system designed for audit, rotation, and access control.
| Pattern | How It Works | Best Fit |
|---|---|---|
| External Secrets Operator | Syncs remote provider values into Kubernetes Secrets. | AWS Secrets Manager, Parameter Store, Vault, GCP, Azure. |
| Secrets Store CSI Driver | Mounts external secrets into Pods, optionally syncs to Kubernetes Secret. | Runtime mount without storing every value as native Secret. |
| SOPS | Encrypts secret manifests in Git with KMS/PGP/age. | GitOps repos that need encrypted declarative manifests. |
| Sealed Secrets | Encrypts Secret manifests for one cluster/controller key. | Simple Git-safe Secret delivery per cluster. |
| Vault Agent Injector | Injects Vault secrets into Pods via sidecar/init/template. | Vault-heavy platforms with dynamic credentials. |
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: app
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: password
remoteRef:
key: prod/web-api/db
property: passwordRBAC Risk
Secret access is high privilege. A subject that can get or list Secrets may be able to read database passwords, API keys, TLS private keys, image pull tokens, and app credentials.
kubectl auth can-i get secrets -n app
kubectl auth can-i list secrets -n app
kubectl auth can-i get secrets --all-namespaces
kubectl auth can-i get secrets -n app \
--as=system:serviceaccount:app:web-apiGitOps And Helm
- Do not commit plaintext secrets: avoid real values in Helm
values.yaml, Kustomize overlays, or raw manifests. - Prefer references: Git should usually contain ExternalSecret, SOPS-encrypted Secret, or SealedSecret resources.
- Watch rendered output: Helm template output, CI logs, ArgoCD diffs, and failed manifests can expose values.
- Separate config from secret: use ConfigMaps for non-sensitive settings and Secrets only for sensitive values.
Debugging Workflow
NS=<namespace>
SECRET=<secret-name>
DEPLOY=<deployment>
kubectl get secret "$SECRET" -n "$NS" -o yaml
kubectl describe secret "$SECRET" -n "$NS"
kubectl get deploy "$DEPLOY" -n "$NS" -o yaml | grep -A20 -E 'secretKeyRef|secretName|imagePullSecrets'
kubectl describe pod -n "$NS" -l app=<app-label>
kubectl logs -n "$NS" deploy/"$DEPLOY" --tail=100
# External Secrets Operator.
kubectl get externalsecret,secretstore,clustersecretstore -A
kubectl describe externalsecret "$SECRET" -n "$NS"Symptom To Cause
| Symptom | Likely Cause | Check First |
|---|---|---|
| Pod fails with missing Secret | Secret not in same namespace or name mismatch. | Pod events and kubectl get secret -n ns. |
| Pod fails with missing key | secretKeyRef.key does not exist. | describe secret keys and Deployment YAML. |
| App still uses old password | Env var not refreshed or app did not reread mounted file. | Rollout restart and app reload behavior. |
| ImagePullBackOff | Bad or missing registry Secret. | imagePullSecrets, Secret type, Pod events. |
| ExternalSecret not syncing | Provider auth, remote key, store ref, RBAC, controller issue. | ExternalSecret status and ESO controller logs. |
| Secret exists but app Forbidden | ServiceAccount lacks permission if app reads API directly. | RBAC can-i as workload ServiceAccount. |
| Secret leaked in logs | App/CI/Helm printed values. | Rotate immediately and remove exposed output. |
Production Patterns
- Enable encryption at rest: use provider-supported etcd/envelope encryption.
- Restrict RBAC: avoid broad
get/list/watch secretspermissions. - Use external source of truth: keep rotation and audit in Vault, cloud secret manager, or approved platform tooling.
- Rotate with evidence: know which Deployments restarted and which clients picked up new values.
- Keep cert lifecycle separate: TLS Secret storage belongs here; certificate issuance and renewal belong on Certificate Management.
Safe Change Pattern
- Identify the true source of the secret: cloud store, Vault, SOPS, SealedSecret, ESO, Helm, or manual Secret.
- Update the source of truth, not just the live Kubernetes Secret, unless it is an approved emergency action.
- Verify the synced Kubernetes Secret metadata, keys, and consuming workload spec.
- Restart or reload workloads that read values only at startup.
- After rotation, test the app path and revoke the old credential where the external system supports it.