Secrets
Manage sensitive data like passwords and API keys using Kubernetes Secrets. Learn best practices for secret management.
Secrets
In the Previous Tutorial, we learned how to use ConfigMaps for configuration. But there's one thing ConfigMaps should NEVER hold — sensitive data. Passwords, API keys, certificates — these need special treatment.
You can't just throw your database password in a ConfigMap. They're stored in plain text, visible to anyone with cluster access. That's like writing your PIN on a post-it note and sticking it to your debit card.
Secrets provide a way to store and manage sensitive data with some additional safeguards.
What is a Secret?
So Secrets are encrypted ConfigMaps?
Not exactly — and this is important. A Secret is like a ConfigMap, but for sensitive data. Key differences:
- Data is base64-encoded (not encrypted by default!)
- Access can be restricted via RBAC
- Kubernetes avoids writing Secrets to disk when possible
- Can be encrypted at rest with additional configuration
Important reality check: Kubernetes Secrets are base64-encoded, NOT encrypted. Anyone with API access can decode them in about 2 seconds. For true security, use external secret managers (HashiCorp Vault, AWS Secrets Manager) or enable encryption at rest.
Wait, base64 isn't encryption? What's the point then?
Yeah, base64 is just encoding — like pig latin for data. The point of Secrets isn't encryption, it's separation of concerns and access control. Kubernetes can restrict who can read Secrets, even if the encoding itself is trivial to reverse.
Secret Types
| Type | Use Case |
|---|---|
Opaque | Generic secrets (default) |
kubernetes.io/basic-auth | Username/password |
kubernetes.io/ssh-auth | SSH private key |
kubernetes.io/tls | TLS certificate and key |
kubernetes.io/dockerconfigjson | Docker registry credentials |
Create a Secret
Just like ConfigMaps, there are several ways to create Secrets. Let's go through them.
Method 1: From Literal Values
The quick way:
kubectl create secret generic db-credentials
--from-literal=username=admin
--from-literal=password=supersecret123
Check it:
```bash
kubectl get secret db-credentials -o yaml
Output:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
password: c3VwZXJzZWNyZXQxMjM=
username: YWRtaW4=
The values are base64-encoded. Look at that gibberish! Let's decode them to prove they're reversible:
echo "c3VwZXJzZWNyZXQxMjM=" | base64 -d
# Output: supersecret123
See? Two seconds. That's why base64 isn't security — it's just data encoding.
Method 2: From Files
Create files with secret data:
echo -n 'admin' > ./username.txt
echo -n 'supersecret123' > ./password.txt
Create Secret:
kubectl create secret generic db-credentials-file \
--from-file=username=./username.txt \
--from-file=password=./password.txt
Clean up the files:
rm ./username.txt ./password.txt
Method 3: YAML Definition
You can define Secrets in YAML, but you must base64-encode values yourself. A bit annoying, but here goes:
echo -n 'admin' | base64
# YWRtaW4=
echo -n 'supersecret123' | base64
# c3VwZXJzZWNyZXQxMjM=
Create db-secret.yaml:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: YWRtaW4=
password: c3VwZXJzZWNyZXQxMjM=
Or use stringData to avoid manual encoding (much nicer!):
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
username: admin
password: supersecret123
Kubernetes encodes stringData automatically. No manual base64 gymnastics! Apply:
kubectl apply -f db-secret.yaml
Use Secrets in Pods
This works exactly like ConfigMaps — same patterns, different object name. If you understood ConfigMaps, you already know this.
1. Environment Variables (Single Keys)
apiVersion: v1
kind: Pod
metadata:
name: app-with-secrets
spec:
containers:
- name: app
image: nginx
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
Apply and verify:
kubectl apply -f app-pod.yaml
kubectl exec app-with-secrets -- env | grep DB_
DB_USERNAME=admin
DB_PASSWORD=supersecret123
2. Environment Variables (All Keys)
apiVersion: v1
kind: Pod
metadata:
name: app-all-secrets
spec:
containers:
- name: app
image: nginx
envFrom:
- secretRef:
name: db-credentials
3. Volume Mounts
Mount Secrets as files:
apiVersion: v1
kind: Pod
metadata:
name: app-secret-volume
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: db-credentials
Each key becomes a file inside the container:
kubectl apply -f app-secret-volume.yaml
kubectl exec app-secret-volume -- ls /etc/secrets
# Output: password username
kubectl exec app-secret-volume -- cat /etc/secrets/password
# Output: supersecret123
The files contain the decoded values, not the base64 gibberish. Kubernetes handles the decoding for you.
Set file permissions for extra security:
volumes:
- name: secret-volume
secret:
secretName: db-credentials
defaultMode: 0400 # Read-only for owner
TLS Secrets
Store TLS certificates for HTTPS.
Create a self-signed certificate (for testing):
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key -out tls.crt \
-subj "/CN=example.com"
Create the Secret:
kubectl create secret tls my-tls-secret \
--cert=tls.crt \
--key=tls.key
Or with YAML:
apiVersion: v1
kind: Secret
metadata:
name: my-tls-secret
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded-cert>
tls.key: <base64-encoded-key>
Use in a Pod:
volumeMounts:
- name: tls
mountPath: /etc/tls
readOnly: true
volumes:
- name: tls
secret:
secretName: my-tls-secret
Docker Registry Secrets
Pull images from private registries.
Create the Secret:
kubectl create secret docker-registry my-registry-secret \
--docker-server=registry.example.com \
--docker-username=myuser \
--docker-password=mypassword \
--docker-email=user@example.com
Use in a Pod:
apiVersion: v1
kind: Pod
metadata:
name: private-image-pod
spec:
containers:
- name: app
image: registry.example.com/myapp:latest
imagePullSecrets:
- name: my-registry-secret
Or attach to a ServiceAccount (applies to all Pods using that account):
kubectl patch serviceaccount default \
-p '{"imagePullSecrets": [{"name": "my-registry-secret"}]}'
Practical Example: Database Connection
Let's put it all together with a real-world scenario — running a Postgres database with credentials from a Secret.
Create a Secret for database credentials:
apiVersion: v1
kind: Secret
metadata:
name: postgres-credentials
type: Opaque
stringData:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: "P@ssw0rd!2024"
POSTGRES_DB: myapp
Use in a Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
envFrom:
- secretRef:
name: postgres-credentials
ports:
- containerPort: 5432
The database container reads credentials from environment variables automatically. No hardcoded passwords anywhere. Your security team will love you.
Security Best Practices
Alright, let's get serious for a moment. Secrets are only as secure as how you handle them.
1. Enable Encryption at Rest
By default, Secrets are stored unencrypted in etcd. Yeah, that's not great. Enable encryption:
# encryption-config.yaml (on control plane)
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-32-byte-key>
- identity: {}
2. Use RBAC
Restrict who can read Secrets:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-credentials"] # Specific secret only
verbs: ["get"]
3. Avoid Secrets in Pod Specs
Never put Secret values directly in your Pod YAML. That defeats the whole purpose!
# Bad — value visible in Pod spec (why even use Secrets??)
env:
- name: PASSWORD
value: "mysecretpassword"
# Good — value hidden in Secret object
env:
- name: PASSWORD
valueFrom:
secretKeyRef:
name: my-secret
key: password
4. Use External Secret Managers
For production, Kubernetes Secrets alone might not cut it. Consider the big guns:
- HashiCorp Vault with the Vault Agent Injector
- AWS Secrets Manager with External Secrets Operator
- Azure Key Vault with CSI driver
- Sealed Secrets for GitOps workflows
These provide real encryption, rotation, auditing, and all the security goodies.
5. Rotate Secrets Regularly
Update Secrets periodically. For volume mounts, Pods pick up changes automatically. For environment variables, restart the Pods.
Inspect Secrets
# List Secrets
kubectl get secrets
# View Secret (encoded)
kubectl get secret db-credentials -o yaml
# Decode a specific value
kubectl get secret db-credentials -o jsonpath='{.data.password}' | base64 -d
# Describe (shows metadata, not values)
kubectl describe secret db-credentials
Clean Up
kubectl delete pod app-with-secrets app-all-secrets app-secret-volume 2>/dev/null
kubectl delete secret db-credentials db-credentials-file my-tls-secret 2>/dev/null
rm tls.key tls.crt 2>/dev/null
What's Next?
You've learned to manage configuration (ConfigMaps) and sensitive data (Secrets). But here's another problem — containers are stateless. When they restart, all data is lost. Your database just lost everything? Oops.
In the next tutorial, you'll learn about Persistent Volumes — how to give your applications durable storage that survives Pod restarts. Because data matters. Let's go!