KubernetesXXI · SecretsSecrets
Consuming Secrets — env vars, mounted files, image pull credentials
What you'll learn
- Configure env-var and file-mount consumption of Secrets
- Use imagePullSecrets for private registry credentials
- Choose the right consumption pattern for a credential class
- Reason about the security trade-offs of each pattern
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
Secrets can be consumed by Pods in three patterns: as
environment variables, as mounted files, or as
imagePullSecrets for private image registry credentials.
Each pattern has security trade-offs; choosing the right
one is part of the production discipline. This lesson
covers the patterns and the visibility chain for each.
Pattern 1: env vars
spec:
containers:
- name: web
image: web:v1
env:
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
- name: DATABASE_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
envFrom:
- secretRef:
name: db-credentials
The container’s process sees DATABASE_PASSWORD=s3cr3t in
its environment. The value is set at container start and is
frozen until the Pod restarts.
flowchart LR
A[Secret] -->|secretKeyRef| B[Container env]
B --> C["/proc/<pid>/environ"]
C --> D[Application]
C --> E["Any process in Pod<br/>can read"]
Pattern 2: mounted files
spec:
containers:
- name: web
image: web:v1
volumeMounts:
- name: db-credentials
mountPath: /etc/db
readOnly: true
volumes:
- name: db-credentials
secret:
secretName: db-credentials
defaultMode: 0400
The container sees files at /etc/db/:
/etc/db/username
/etc/db/password
Each file is owned by the user the container runs as (or
root if runAsUser is not set) and has the mode defaultMode
(default 0644, recommended 0400 for credentials).
flowchart LR
A[Secret] -->|secret volume| B[Container filesystem]
B --> C["/etc/db/password"]
B --> D["/etc/db/username"]
C --> E[Application reads]
The kubelet projects the Secret as files with the specified mode. The application reads the file directly; the value is not visible to other processes unless they have file-system access.
Secret volume updates
flowchart TB
A[Secret updated] --> B[Kubelet watches]
B --> C{Period elapsed?}
C -->|yes| D["Kubelet updates<br/>mount contents"]
C -->|no| E[Wait]
Like ConfigMap volumes, Secret volumes are eventually consistent (default syncPeriod ~60s). The application must detect the change and reload.
Optional keys
volumes:
- name: db-credentials
secret:
secretName: db-credentials
optional: true
If the Secret does not exist or the key is missing, the Pod
is still scheduled (no blocking). For credentials that are
optional, this is correct. For credentials that the Pod
cannot function without, set optional: false.
Pattern 3: imagePullSecrets
spec:
containers:
- name: web
image: registry.example.com/team/web:v1
imagePullSecrets:
- name: reg-credentials
The kubelet uses the credentials to authenticate to the
private registry when pulling the image. The Secret type
is kubernetes.io/dockerconfigjson or
kubernetes.io/dockerconfig.
flowchart LR
A[kubelet] -->|imagePullSecrets| B[Registry]
B -->|auth| C[Pull image]
C --> D[Start container]
The kubelet pulls the image before starting the container; the credentials are not visible to the container after pull. The image is on the node’s local registry; the container starts with the cached image.
flowchart TB
A["imagePullSecrets:<br/>reg-credentials"] --> B[kubelet]
B --> C[Read .dockerconfigjson]
C --> D[Auth to registry]
D --> E[Pull image]
E --> F[Cache on node]
F --> G["Container starts<br/>no credentials visible"]
Comparing the patterns
| Pattern | Visibility | Updates | Use case |
|---|---|---|---|
| env vars | /proc/<pid>/environ | Frozen at start | Connection strings, simple credentials |
| Mounted files | File in container FS | Eventually consistent | TLS keys, certificates, structured credentials |
| imagePullSecrets | kubelet only | N/A | Registry authentication |
Security trade-offs
Env vars
- Visibility: any process in the Pod.
- Logging: env vars may appear in error logs (e.g., when a library logs the connection string on failure).
- Core dumps: env vars are part of the process state; a core dump on crash writes the env to a file.
- Updates: frozen; the Pod must restart.
Mounted files
- Visibility: any process with file-system read access. File mode 0400 limits this to the container’s user.
- Logging: the file is not in the process env unless the application loads it; accidental logging is rare.
- Updates: eventual consistency; the application can reload.
- Persistence: the file is in tmpfs; it is not on disk. A node reboot clears the file (the kubelet re-mounts from the Secret).
imagePullSecrets
- Visibility: kubelet only; the container never sees the credentials.
- Logging: the kubelet logs the registry interaction; the credentials are not in the logs.
- Updates: the kubelet reads the Secret each time it pulls (which is rare after the first pull per node).
Real-world patterns
Pattern: TLS for an Ingress
apiVersion: v1
kind: Secret
metadata:
name: web-tls
type: kubernetes.io/tls
data:
tls.crt: <base64 cert>
tls.key: <base64 key>
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
spec:
tls:
- hosts: [web.example.com]
secretName: web-tls
rules:
- host: web.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
The Ingress controller mounts the Secret and serves the TLS material. The Pod never sees the private key.
Pattern: registry credentials
kubectl create secret docker-registry reg-credentials \
--docker-server=registry.example.com \
--docker-username=service-account \
--docker-password=$(vault read -field=password secrets/registry) \
-n prod
The Secret’s data is sourced from an external secret manager; the Secret in the cluster is a rendering.
Pattern: database credentials
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData:
username: app
password: s3cr3t
database: app_prod
Consumed as env vars:
env:
- name: DATABASE_URL
value: "postgres://$(DB_USERNAME):$(DB_PASSWORD)@db/$(DB_NAME)"
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
- name: DB_NAME
valueFrom:
secretKeyRef:
name: db-credentials
key: database
The application reads DATABASE_URL at startup; the
individual credentials are in env vars but not in the
connection string.
Quiz
Knowledge check · 4 questions
Q1. What is the recommended pattern for mounting a Secret as a TLS certificate in an Ingress?
Q2. imagePullSecrets is mounted into the container's filesystem after the image is pulled.
Q3. Your team mounts a Secret with defaultMode 0644 as a file in a container. The container runs as a non-root user. The container cannot read the Secret file. Diagnose and remediate.
Secret mounted as /etc/db/password. defaultMode 0644 (world-readable). The container runs as runAsUser 10000. The non-root user cannot read the file.
Q4. Why is mounting a Secret as env vars a security risk, and what is the safer pattern?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Mount as files for credentials the application reads from files. TLS keys, certificate chains, SSH keys.
- Use env vars for connection strings. A single
DATABASE_URLenv var is fine; the individual credentials can be in separate env vars. - Set
defaultMode: 0400on Secret volumes. The default0644is readable by any process in the container. - Avoid logging Secrets. A library that logs the connection string on error is leaking the password.
- Audit imagePullSecrets. A Pod that pulls from a
private registry without
imagePullSecretswill fail at pull time; verify the Secret exists.
Secrets are the workhorse of Kubernetes credential management. The consumption pattern determines the visibility chain. Operators who choose deliberately have Secrets that work securely.