KubernetesXXI · SecretsSecrets
Secrets — base64 is not encryption
What you'll learn
- Describe what Secrets are for and the Secret types (Opaque, kubernetes.io/tls, dockerconfigjson, service-account-token)
- Explain why base64 is not encryption
- Reason about the visibility of Secrets in etcd and via the API
- Apply the right Secret type to a real workload
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
A Secret is the Kubernetes object for sensitive data:
passwords, tokens, certificates, SSH keys. Like ConfigMaps,
Secrets are key-value maps. Unlike ConfigMaps, Secrets have
specific types (kubernetes.io/tls, dockerconfigjson,
etc.) and stricter handling — but the underlying storage is
plaintext unless encryption at rest is configured. This
lesson covers the types, the encoding (base64 is not
encryption), and the visibility rules.
What a Secret is
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: prod
type: Opaque
data:
username: YXBw # base64 of "app"
password: czNjcjN0 # base64 of "s3cr3t"
stringData:
database_url: postgres://app:s3cr3t@db.prod/data
A Secret has data (base64-encoded values) and
stringData (plaintext values that the API server
base64-encodes on write). The two are equivalent; stringData
is more convenient for authoring.
flowchart LR
A["Authoring: stringData<br/>plaintext"] -->|API server| B["Encoded: data<br/>base64"]
B -->|etcd| C[Stored plaintext]
C -->|reader with get secret| D[Plaintext in memory]
The encoding happens once on write. Reading the Secret
returns the encoded values; the API server does not
auto-decode for kubectl get (with --output it does).
Base64 is not encryption
Base64 is an encoding scheme, not an encryption scheme. It maps binary data to a printable string. The mapping is reversible by anyone who knows base64:
$ echo 'czNjcjN0' | base64 -d
s3cr3t
Secret types
Kubernetes defines several Secret types with specific schemas:
| Type | Schema | Use |
|---|---|---|
Opaque | Arbitrary key-value | Generic credentials |
kubernetes.io/tls | tls.crt, tls.key | TLS certificates |
kubernetes.io/dockerconfigjson | .dockerconfigjson | Image registry credentials |
kubernetes.io/dockerconfig | .dockercfg | Legacy registry credentials |
kubernetes.io/basic-auth | username, password | HTTP basic auth |
kubernetes.io/ssh-auth | ssh-privatekey | SSH private keys |
kubernetes.io/service-account-token | token, ca.crt, namespace | ServiceAccount tokens |
bootstrap.kubernetes.io/token | token-id, token-secret, signing-key | Node bootstrap tokens |
The type is metadata; the API server does not validate the
content for Opaque but does for typed secrets
(kubernetes.io/tls requires tls.crt and tls.key).
kubernetes.io/tls
apiVersion: v1
kind: Secret
metadata:
name: web-tls
type: kubernetes.io/tls
data:
tls.crt: <base64 of cert PEM>
tls.key: <base64 of private key PEM>
Used by Ingress and Pods that need TLS material. The Ingress controller mounts this Secret and serves the cert.
dockerconfigjson
apiVersion: v1
kind: Secret
metadata:
name: reg-credentials
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: |
{
"auths": {
"registry.example.com": {
"username": "service-account",
"password": "...",
"auth": "..."
}
}
}
Used as imagePullSecrets on a Pod to authenticate to a
private image registry.
service-account-token
Created automatically when a ServiceAccount is referenced. Mounted into Pods that use the ServiceAccount; the kubelet periodically refreshes the token.
Creating Secrets
From literals:
kubectl create secret generic db-credentials -n prod \
--from-literal=username=app \
--from-literal=password=s3cr3t
From files:
kubectl create secret tls web-tls -n prod \
--cert=tls.crt --key=tls.key
From a docker config:
kubectl create secret docker-registry reg-credentials -n prod \
--docker-server=registry.example.com \
--docker-username=service-account \
--docker-password=...
From a manifest:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
password: s3cr3t
Where Secrets live
flowchart LR
A[Pod uses Secret] --> B[API server]
B --> C[etcd]
C -->|plaintext| D["Anyone with<br/>etcd read access"]
B -->|API response| E[Authorized reader]
E --> F[Pod container]
By default, Secrets are stored plaintext in etcd. Encryption at rest is configured separately and is not the default.
The visibility chain:
- etcd direct access — anyone with file-system or network access to etcd can read the records.
- API server access — anyone with
get secretpermission can read viakubectl get secret -o yaml. - Pod container access — Pods that mount the Secret can read the file or env var.
- Node access — anyone with root on a node where the
Pod is running can read the file from
/var/lib/kubelet/pods/<uid>/volumes/....
Encryption at rest protects the first; RBAC protects the second; Pod-level security contexts and Linux capabilities protect the third and fourth.
Inspecting a Secret
kubectl get secret db-credentials -n prod -o yaml
# apiVersion: v1
# kind: Secret
# metadata:
# name: db-credentials
# data:
# password: czNjcjN0 # base64
# username: YXBw
kubectl get secret db-credentials -n prod -o jsonpath='{.data.password}' | base64 -d
# s3cr3t
$ kubectl describe secret db-credentials -n prodName: db-credentials
Namespace: prod
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
password: 6 bytes
username: 3 byteskubectl describe does not show the Secret values; only the
sizes. This is a UX guard, not a security guard — anyone
with get secret can still kubectl get -o yaml.
Quiz
Knowledge check · 4 questions
Q1. Which Secret type is correct for a TLS certificate with a private key?
Q2. Base64-encoding a Secret value provides real encryption.
Q3. Your team has a Secret that contains a database password. The CI pipeline logs the Secret's YAML for debugging. The password leaks to logs. Diagnose and remediate.
CI job runs kubectl get secret db-credentials -o yaml and prints the output to the build log. The base64-encoded password is visible in the log.
Q4. Explain why a Secret that is only base64-encoded is not encrypted, and what protections Kubernetes actually provides.
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Secrets are not encrypted by default. Encryption at rest is opt-in; configure it before storing credentials.
- Use the typed Secret variants.
kubernetes.io/tlsfor TLS material,dockerconfigjsonfor registry credentials. The typed format is validated and prevents schema mistakes. - Treat
get secretas a privileged permission. RBAC should restrict it to identities that need it; audit every binding that grantsget. - Avoid
kubectl get secret -o yamlin shared logs. The values are plaintext in the output; a CI pipeline that prints Secret YAMLs is leaking credentials. - Use external secret managers for production credentials. Vault, AWS Secrets Manager, External Secrets Operator. The Secret object is the rendering, not the source.
Secrets are the workhorse of Kubernetes credential management. They are not a security mechanism by themselves; they are a storage object that needs encryption, RBAC, and operational discipline to be secure.