⌂ Home

Secrets in Kubernetes

Interactive guide to storing and consuming sensitive data — passwords, tokens, TLS certificates, and SSH keys — without baking them into images or manifests.

A Secret holds confidential data in base64-encoded form and is delivered to Pods through environment variables or volume mounts. Kubernetes keeps Secrets separate from Pod specs so you can manage access, rotation, and audit independently.

Core Model

Understand the Concept First

Separate sensitive data

Secrets decouple confidential values (DB passwords, API keys, TLS certs) from container images and Pod definitions, enabling independent lifecycle management.

Base64 ≠ encryption

Values are base64-encoded in the API, not encrypted by default. Enable etcd encryption-at-rest or use an external secret store for real protection.

Two consumption paths

Inject as environment variables (secretKeyRef) for simple access, or mount as files (volumeMount) for certificates and config files that apps expect on disk.

Namespace-scoped

Secrets live in a namespace. A Pod can only reference Secrets in the same namespace, keeping blast radius contained.

ConfigMaps are for non-sensitive data; Secrets are the correct object for anything you would not want visible in kubectl get configmap -o yaml.
Secret Types

Built-in Secret Types

TypeValue of type:Typical Use
OpaqueOpaqueGeneral purpose — arbitrary key/value pairs (default)
Docker Registrykubernetes.io/dockerconfigjsonImage pull credentials for private registries
TLSkubernetes.io/tlsTLS certificate + private key pair
Basic Authkubernetes.io/basic-authUsername / password pair
SSH Authkubernetes.io/ssh-authSSH private key
Tokenbootstrap.kubernetes.io/tokenBootstrap token for node joining
Service Accountkubernetes.io/service-account-tokenAuto-mounted SA token (legacy; BoundServiceAccountTokenVolume is the modern path)
Use the explicit type: field when creating a Secret so Kubernetes can validate required keys (e.g. tls.crt + tls.key for TLS secrets).
Lifecycle Flow

Secret Delivery Flow

Secret Consumption Patterns Secret db-credentials username: YWRtaW4= password: cEBzc3cwcmQ= envFrom Pattern 1: All Keys as Env Vars envFrom: - secretRef: name: db-credentials Container sees: $username = "admin" $password = "p@ssw0rd" valueFrom Pattern 2: Selective Keys env: - name: DB_PASS valueFrom: secretKeyRef: name: db-credentials key: password volumeMount Pattern 3: Files in Volume volumes: - name: creds secret: secretName: db-credentials /creds/username /creds/password Update Propagation Secret Updated password: bmV3UGFzcw== kubectl edit secret or kubectl apply kubelet sync Volume Mount ✓ Auto-updates (~1 min) Files reflect new value App must re-read the file Env Variables ✗ NO auto-update Set at Pod start, immutable Must recreate Pod to pick up changes
Volume-mounted Secrets auto-update when the Secret changes. Environment variables are frozen at Pod start; update the Secret then perform a rolling restart to pick up new values.
YAML and Commands

Examples You Can Recognize Quickly

Create an Opaque Secret (imperative)
# From literal values
kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password='p@ssw0rd'

# From a file
kubectl create secret generic tls-cert \
  --from-file=tls.crt=./server.crt \
  --from-file=tls.key=./server.key
Opaque Secret YAML (declarative)
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
type: Opaque
data:                        # base64-encoded values
  username: YWRtaW4=         # echo -n 'admin' | base64
  password: cEBzc3cwcmQ=    # echo -n 'p@ssw0rd' | base64
stringData (plain-text convenience)
apiVersion: v1
kind: Secret
metadata:
  name: app-secret
type: Opaque
stringData:                  # Kubernetes base64-encodes these for you
  api-key: my-super-secret-key
  connection-string: "postgresql://user:pass@db:5432/mydb"
TLS Secret
# Imperative
kubectl create secret tls my-tls-secret \
  --cert=./tls.crt \
  --key=./tls.key

# Declarative
apiVersion: v1
kind: Secret
metadata:
  name: my-tls-secret
type: kubernetes.io/tls
data:
  tls.crt: LS0tLS1CRUdJTi...   # base64 of cert PEM
  tls.key: LS0tLS1CRUdJTi...   # base64 of key PEM
Docker Registry Secret
kubectl create secret docker-registry regcred \
  --docker-server=https://index.docker.io/v1/ \
  --docker-username=myuser \
  --docker-password=mypass \
  --docker-email=me@example.com

# Reference in a Pod spec
spec:
  imagePullSecrets:
  - name: regcred
Consume Secret as Env Vars
apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: app
    image: busybox
    command: ["sh", "-c", "echo $DB_USER $DB_PASS && sleep 3600"]
    env:
    - name: DB_USER
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: username
    - name: DB_PASS
      valueFrom:
        secretKeyRef:
          name: db-credentials
          key: password
Mount Secret as Volume
apiVersion: v1
kind: Pod
metadata:
  name: secret-vol-pod
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: creds
      mountPath: /etc/secrets
      readOnly: true
  volumes:
  - name: creds
    secret:
      secretName: db-credentials
      defaultMode: 0400    # read-only for owner
Decision Guide

Secrets vs ConfigMaps

AspectSecretConfigMap
PurposeSensitive / confidential dataNon-sensitive configuration
EncodingBase64 by default; supports stringDataPlain text
Size limit1 MiB per Secret1 MiB per ConfigMap
etcd storageEncrypted-at-rest when configuredStored in plain text
RBACTypically restricted to specific rolesOften broader access
Volume default mode0644 (recommended: 0400)0644
Typed validationYes — type: enforces required keysNo typed enforcement
Auto-update (volume)Yes (~1 min kubelet sync)Yes (~1 min kubelet sync)
Auto-update (env)No — Pod restart requiredNo — Pod restart required
If in doubt, use a Secret. Accidentally putting a password in a ConfigMap exposes it to anyone with get configmap permissions.
Hardening

Security Best Practices

Enable encryption at rest

Configure EncryptionConfiguration on the API server so Secrets are encrypted in etcd rather than stored as plain base64.

Restrict RBAC

Limit get, list, and watch on Secrets to service accounts and users that genuinely need them.

Use external secret stores

For production, integrate with HashiCorp Vault, AWS Secrets Manager, or GCP Secret Manager via CSI driver or operator (e.g. External Secrets Operator).

Avoid env vars for high-value secrets

Env vars appear in /proc, crash dumps, and logs. Prefer volume mounts with readOnly: true and restricted defaultMode.

Rotate regularly

Treat Secrets as ephemeral. Update values and perform a rolling restart; automate rotation with controllers.

Never commit to Git

Add Secret YAML files to .gitignore. Use sealed-secrets or SOPS if you need GitOps workflows for sensitive data.

⚠️ Base64 encoding is not encryption. Anyone with API access can decode a Secret value with echo <value> | base64 -d.
kubectl Reference

Essential Secret Commands

ActionCommand
Create from literalskubectl create secret generic NAME --from-literal=key=value
Create from filekubectl create secret generic NAME --from-file=key=filepath
Create TLS secretkubectl create secret tls NAME --cert=path --key=path
Create docker-registrykubectl create secret docker-registry NAME --docker-server=... --docker-username=... --docker-password=...
List secretskubectl get secrets
Describe (metadata only)kubectl describe secret NAME
View decoded valuekubectl get secret NAME -o jsonpath='{.data.key}' | base64 -d
Edit in-placekubectl edit secret NAME
Deletekubectl delete secret NAME
Dry-run YAML generationkubectl create secret generic NAME --from-literal=k=v --dry-run=client -o yaml
Use It Well

Practice and Real-World Thinking

Database credentials

Store DB username and password in a Secret, reference via secretKeyRef in the Deployment. Rotate by updating the Secret and performing a rolling restart.

TLS termination

Create a kubernetes.io/tls Secret and reference it in an Ingress resource for HTTPS termination at the ingress controller.

Private registry access

Create a docker-registry Secret and reference it via imagePullSecrets in the Pod spec or patch it into the ServiceAccount.

SSH keys for Git sync

Mount an ssh-auth Secret into a sidecar that pulls code from a private Git repository.

ConfigMap + Secret together

Use a ConfigMap for app settings and a Secret for credentials; mount both into the same Pod for a clean separation of concerns.

Immutable Secrets

Set immutable: true on Secrets that should never change (K8s 1.21+). Protects against accidental edits and improves kubelet performance.