Kubernetes Secrets: Safe Consumption
base64 is NOT Encryption
Anyone with kubectl get secret access can read your passwords in plaintext. Two commands:
kubectl create namespace secrets-demo
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=SuperSecretPassword123 \
-n secrets-demo
kubectl get secret db-credentials -n secrets-demo -o yaml
Look at the data section. That value is base64. Decode it:
kubectl get secret db-credentials -n secrets-demo \
-o jsonpath='{.data.password}' | base64 -d
echo ""
Expected: SuperSecretPassword123
No special tools needed. Zero effort. The point of Kubernetes Secrets is not encryption — it is keeping credentials out of your manifests and images, where they would end up in git history, container registries, and build logs permanently.
What We Are Avoiding: Hardcoded Credentials
The anti-pattern is embedding credentials directly in a manifest or baking them into an image. Here is what that looks like — do not do this:
cat > pod-hardcoded.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: pod-hardcoded
namespace: secrets-demo
spec:
containers:
- name: app
image: public.ecr.aws/docker/library/busybox:1.36
command: ["sh", "-c", "echo connecting to db with $DB_PASS && sleep 3600"]
env:
- name: DB_PASS
value: "SuperSecretPassword123"
restartPolicy: Never
EOF
kubectl apply -f pod-hardcoded.yaml
kubectl get pod pod-hardcoded -n secrets-demo -o yaml | grep -A2 "env:"
The password is visible to anyone who can read the Pod spec — kubectl get pod, kubectl describe pod, the API server audit log, and anything that exports cluster state. It also lives permanently in your git history the moment the file is committed.
Pattern 1: Environment Variable from Secret
The credential is stored in a Secret object and injected at runtime — it never appears in the manifest:
cat > pod-env-secret.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: pod-env-secret
namespace: secrets-demo
spec:
containers:
- name: app
image: public.ecr.aws/docker/library/busybox:1.36
command: ["sh", "-c", "echo DB_USER=$DB_USER && echo DB_PASS set: $(test -n \"$DB_PASS\" && echo yes || echo no) && sleep 3600"]
env:
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: db-credentials
key: password
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
restartPolicy: Never
EOF
kubectl apply -f pod-env-secret.yaml
kubectl wait --for=condition=Ready pod/pod-env-secret -n secrets-demo --timeout=30s
kubectl logs pod/pod-env-secret -n secrets-demo
The manifest references the Secret by name — the value itself is never written into the YAML.
Pattern 2: Volume Mount from Secret
The Secret is mounted as files inside the container. The application reads credentials from disk at runtime:
cat > pod-volume-secret.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
name: pod-volume-secret
namespace: secrets-demo
spec:
containers:
- name: app
image: public.ecr.aws/docker/library/busybox:1.36
command: ["sh", "-c", "echo username=$(cat /etc/db-creds/username) && echo password=$(cat /etc/db-creds/password) && sleep 3600"]
volumeMounts:
- name: db-creds
mountPath: /etc/db-creds
readOnly: true
volumes:
- name: db-creds
secret:
secretName: db-credentials
restartPolicy: Never
EOF
kubectl apply -f pod-volume-secret.yaml
kubectl wait --for=condition=Ready pod/pod-volume-secret -n secrets-demo --timeout=30s
kubectl logs pod/pod-volume-secret -n secrets-demo
Expected:
username=admin
password=SuperSecretPassword123
Same result — credential never appears in the manifest.
Summary
| Pattern | Credential in manifest? | Credential in git? |
|---|---|---|
| Hardcoded value | Yes | Yes — permanently |
secretKeyRef (env var) | No — reference only | No |
| Volume mount | No — reference only | No |
Both secretKeyRef and volume mounts solve the core problem. The next lab (Vault + External Secrets Operator) goes further — secrets are not stored in Kubernetes at all, and rotation happens without touching a manifest.
Cleanup
kubectl delete namespace secrets-demo
- Previous lesson
- Observability — Detecting Security Anomalies with Prometheus
- Next lesson
- HashiCorp Vault + External Secrets Operator