Secrets: handling sensitive data
A Secret works almost exactly like a ConfigMap, but is meant for passwords, API keys and certificates. Kubernetes treats it more carefully. It is not magic encryption, though, and knowing exactly what it does and does not protect is the whole point of this chapter.
Secret vs ConfigMap
| ConfigMap | Secret | |
|---|---|---|
| For | Settings: URLs, log levels, config files | Passwords, tokens, keys, certificates |
| Values stored as | Plain text | Base64 (encoded, not encrypted) |
| Used in Pods as | env vars or files | env vars or files, the same way |
| Extra care by Kubernetes | None | Sent only to nodes that run a Pod using it; mounted in memory (tmpfs), never written to the node's disk; can be encrypted at rest in etcd; separate access rules |
| Size limit | 1 MiB | 1 MiB |
Base64 is not encryption
Secret values in YAML look scrambled, like aHVudGVyMg==. That is base64: a way to write any bytes as plain letters. There is no key, so anyone can reverse it. Try it, and try the classic newline mistake:
Writing a Secret
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
DB_USER: cG9zdHM= # "posts"
stringData:
DB_PASSWORD: hunter2 # plain text, encoded for you
type: Opaque
The default: "arbitrary data, Kubernetes does not care what". Other types check that certain keys exist; see Secret types.
data
Values must be base64-encoded by you. Easy to get wrong, as the newline demo showed.
stringData
Write values as plain text and Kubernetes encodes them on save. Easier and fewer mistakes. When you read the Secret back, everything appears under data, encoded.
Using it in a Pod
Exactly like a ConfigMap, with secret in place of configMap:
containers:
- name: posts
image: micro-posts:1.0
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_PASSWORD
# or every key at once:
envFrom:
- secretRef:
name: db-credentials
Your app reads process.env.DB_PASSWORD, already decoded. Simple, and fine for many apps.
Downside: env vars leak easily. They show up in crash reports, in kubectl describe-style debugging, in child processes, and in any "print all config" log line someone adds. They also never change until the Pod restarts.
spec:
containers:
- name: posts
image: micro-posts:1.0
volumeMounts:
- name: db
mountPath: /etc/secrets
readOnly: true
volumes:
- name: db
secret:
secretName: db-credentials
defaultMode: 0400 # only the owner can read
// Node
const fs = require('fs');
const password = fs.readFileSync('/etc/secrets/DB_PASSWORD', 'utf8');
Each key becomes a file, decoded, kept in memory (tmpfs) and never written to the node's disk. Files are refreshed when the Secret changes, so you can rotate a password without a restart if your app re-reads the file. Generally the safer choice.
Secret types
| Type | Holds | Used by |
|---|---|---|
Opaque | Anything (default) | Your app: DB passwords, API keys |
kubernetes.io/dockerconfigjson | Registry login | The kubelet, to pull images from a private registry (imagePullSecrets) |
kubernetes.io/tls | tls.crt + tls.key | Ingress, for HTTPS certificates |
kubernetes.io/basic-auth | username + password | Tools that expect that shape |
kubernetes.io/ssh-auth | ssh-privatekey | e.g. cloning private Git repos |
kubernetes.io/service-account-token | An API token | Legacy; modern clusters give Pods short-lived tokens automatically |
Pulling your micro-posts image from a private registry, for example:
$ kubectl create secret docker-registry regcred \
--docker-server=ghcr.io --docker-username=me --docker-password=$TOKEN
# in the Pod spec
spec:
imagePullSecrets:
- name: regcred
containers:
- name: posts
image: ghcr.io/me/micro-posts:1.0
Creating with kubectl
$ kubectl create secret generic db-credentials \ --from-literal=DB_USER=posts \ --from-literal=DB_PASSWORD='hunter2' secret/db-credentials createdThe easiest way; encoding is done for you, no newline bug. Note: the password ends up in your shell history.
--from-fileavoids that.$ kubectl get secret db-credentials NAME TYPE DATA AGE db-credentials Opaque 2 5s $ kubectl describe secret db-credentials Data ==== DB_PASSWORD: 7 bytes DB_USER: 5 bytesgetanddescribeshow only key names and sizes, not values.$ kubectl get secret db-credentials -o jsonpath='{.data.DB_PASSWORD}' | base64 -d hunter2…but anyone allowed to
getSecrets can read them. That permission is what you must guard.$ kubectl create secret tls posts-tls --cert=tls.crt --key=tls.key secret/posts-tls createdA TLS Secret for HTTPS, used by Ingress later.
What actually keeps it safe
- Access control (RBAC). Only people and apps that truly need it may
getorlistSecrets. Note: anyone who can create a Pod in a namespace can mount any Secret in that namespace. - Encryption at rest. Turn on etcd encryption so a stolen etcd backup does not reveal every password. Managed clusters (EKS, GKE, AKS) offer this, often with a cloud key service (KMS).
- Keep plain Secrets out of Git. Use a tool built for this: Sealed Secrets (encrypt for the cluster, safe to commit), SOPS, or the External Secrets Operator pulling from AWS Secrets Manager, Vault, etc.
- Prefer files over env vars for highly sensitive values, and mount them read-only.
- Separate namespaces for separate teams or apps, so a Secret is only visible where it belongs.
Safe or not?
- Committing
secret.yamlwith base64 values to a private Git repoAnyone with repo access can decode it, and the history keeps it forever. Use Sealed Secrets, SOPS or an external secret store.
- Mounting a Secret as a read-only file with
defaultMode: 0400Good practice: in-memory, read-only, owner-only.
- Logging
process.envat startup "for debugging"Every Secret passed as an env var is now in your log system.
- Putting the DB password in a ConfigMap because "it is the same thing"
ConfigMaps get none of the Secret protections and are often readable by many more people.
- Using
stringDatain a template filled in by your CI from a secret storeFine, as long as the filled-in file is never committed or printed in CI logs.
Recap
- A Secret is a ConfigMap for sensitive values: same
env/envFrom/volume usage. - Base64 is encoding, not encryption. Use
stringDataorkubectl create secretto avoid encoding mistakes. - Types like
dockerconfigjsonandtlsexist for registries and HTTPS. - Real protection comes from RBAC, encryption at rest, and keeping Secrets out of Git.
- Files are safer than env vars, and can be rotated without a restart.
Secrets: common questions
Are Kubernetes Secrets encrypted?
Not by default. Values are only base64-encoded. Protect them with RBAC, encryption at rest in etcd, and by keeping plain Secret files out of Git.