TL;DR

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 sourceVault / AWS / GitK8s SecretAPI + etcdKubeletnode materializeVolume fileEnvironment varVolume-mounted Secrets can update; env vars require Pod restart to pick up new values.

Secret data moves from source to Kubernetes Secret to workload consumption.

Secret Types

TypeUseWatchpoint
OpaqueGeneric app credentials and key-value data.Most common; schema is up to you.
kubernetes.io/tlsTLS cert/key pair in tls.crt and tls.key.Certificate lifecycle belongs on Certificate Management page.
kubernetes.io/dockerconfigjsonPrivate registry pull credentials.Used by imagePullSecrets.
kubernetes.io/service-account-tokenLegacy ServiceAccount token Secret.Prefer projected short-lived tokens where possible.
bootstrap.kubernetes.io/tokenCluster 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.

yamlsecret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: app
type: Opaque
stringData:
  username: app_user
  password: change-me-in-real-secret-store
bashsecret-commands.sh
kubectl 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; echo

Consume 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.

yamldeployment-secret-volume.yaml
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: password

Rotation Behavior

Consumption MethodRotation BehaviorOperational Action
Secret volumeKubelet updates mounted files eventually.App must reread file or be restarted.
Environment variableDoes not update inside a running container.Rollout restart required.
External Secrets Operator synced SecretController refreshes Kubernetes Secret on interval.Still restart app if it reads only at startup.
Secrets Store CSI mountProvider/driver dependent rotation.Confirm sync and app reread behavior.
bashrotate-secret.sh
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 app

External 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.

PatternHow It WorksBest Fit
External Secrets OperatorSyncs remote provider values into Kubernetes Secrets.AWS Secrets Manager, Parameter Store, Vault, GCP, Azure.
Secrets Store CSI DriverMounts external secrets into Pods, optionally syncs to Kubernetes Secret.Runtime mount without storing every value as native Secret.
SOPSEncrypts secret manifests in Git with KMS/PGP/age.GitOps repos that need encrypted declarative manifests.
Sealed SecretsEncrypts Secret manifests for one cluster/controller key.Simple Git-safe Secret delivery per cluster.
Vault Agent InjectorInjects Vault secrets into Pods via sidecar/init/template.Vault-heavy platforms with dynamic credentials.
yamlexternal-secret.yaml
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: password

RBAC 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.

bashsecret-rbac-checks.sh
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-api

GitOps And Helm

  • 1Do not commit plaintext secrets: avoid real values in Helm values.yaml, Kustomize overlays, or raw manifests.
  • 2Prefer references: Git should usually contain ExternalSecret, SOPS-encrypted Secret, or SealedSecret resources.
  • 3Watch rendered output: Helm template output, CI logs, ArgoCD diffs, and failed manifests can expose values.
  • 4Separate config from secret: use ConfigMaps for non-sensitive settings and Secrets only for sensitive values.

Debugging Workflow

bashsecret-debug.sh
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

SymptomLikely CauseCheck First
Pod fails with missing SecretSecret not in same namespace or name mismatch.Pod events and kubectl get secret -n ns.
Pod fails with missing keysecretKeyRef.key does not exist.describe secret keys and Deployment YAML.
App still uses old passwordEnv var not refreshed or app did not reread mounted file.Rollout restart and app reload behavior.
ImagePullBackOffBad or missing registry Secret.imagePullSecrets, Secret type, Pod events.
ExternalSecret not syncingProvider auth, remote key, store ref, RBAC, controller issue.ExternalSecret status and ESO controller logs.
Secret exists but app ForbiddenServiceAccount lacks permission if app reads API directly.RBAC can-i as workload ServiceAccount.
Secret leaked in logsApp/CI/Helm printed values.Rotate immediately and remove exposed output.

Production Patterns

  • 1Enable encryption at rest: use provider-supported etcd/envelope encryption.
  • 2Restrict RBAC: avoid broad get/list/watch secrets permissions.
  • 3Use external source of truth: keep rotation and audit in Vault, cloud secret manager, or approved platform tooling.
  • 4Rotate with evidence: know which Deployments restarted and which clients picked up new values.
  • 5Keep cert lifecycle separate: TLS Secret storage belongs here; certificate issuance and renewal belong on Certificate Management.

Safe Change Pattern

  1. Identify the true source of the secret: cloud store, Vault, SOPS, SealedSecret, ESO, Helm, or manual Secret.
  2. Update the source of truth, not just the live Kubernetes Secret, unless it is an approved emergency action.
  3. Verify the synced Kubernetes Secret metadata, keys, and consuming workload spec.
  4. Restart or reload workloads that read values only at startup.
  5. After rotation, test the app path and revoke the old credential where the external system supports it.