When giving a container a Kubernetes Secret, what are the trade-offs between injecting it as environment variables (`envFrom` / `valueFrom.secretKeyRef`) and mounting it as a volume?
answer
- env = snapshot at exec; rotation needs restart
- volume = tmpfs + kubelet refresh (~1 min)
- atomic swap via ..data symlink
- subPath NEVER updates
- /proc/<pid>/environ, core dumps, child processes
basics
~20 sEnvironment variables are captured once at process start, so rotation needs a Pod restart, and they leak easily via /proc, crash dumps and child processes. Volume files are memory-backed and refreshed by the kubelet within about a minute, so apps that re-read the file pick up rotation — but subPath mounts never update.
solid answer
~60 s**Environment variables** are read into the process at exec time and frozen there. A rotated Secret reaches the container only when the Pod restarts. They also spread easily: `/proc/<pid>/environ`, core dumps, crash reporters, every child process, and any framework that logs its environment on startup. They are convenient — twelve-factor apps expect them — and a missing key fails the Pod fast unless marked `optional: true`. **Volume mounts** project each key as a file on a `tmpfs` (memory-backed) mount, so nothing hits node disk. The kubelet refreshes the contents when the Secret changes — typically within the kubelet sync period plus its cache TTL, so on the order of a minute — and does so atomically by swapping a symlink to a new `..data` directory, so a reader never sees a half-written file. An application that re-reads the file gets rotation with no restart. You can also control `defaultMode` and `fsGroup`. The critical exception: a mount using `subPath` is **not** updated, ever. Default to volumes for anything rotating (TLS material, DB passwords with rotation); env vars are acceptable for static values in simple apps.
code
yaml · 26 linesapiVersion: v1
kind: Pod
metadata:
name: api
spec:
containers:
- name: app
image: registry.example.com/api:2.1.0
env:
- name: DB_PASSWORD # snapshot at start; rotation needs a restart
valueFrom:
secretKeyRef:
name: db-credentials
key: password
volumeMounts:
- name: tls # updated in place by the kubelet
mountPath: /etc/tls
readOnly: true
# - name: tls
# mountPath: /etc/tls/tls.crt
# subPath: tls.crt # NEVER updated - avoid for rotating material
volumes:
- name: tls
secret:
secretName: api-tls
defaultMode: 0400go deeper
Know the headline: env vars are fixed once the container starts, mounted files can be updated by the kubelet, and both are readable inside the Pod.
Explain the refresh mechanism (tmpfs, kubelet sync, atomic ..data symlink swap), the subPath exception, and the concrete env-var leak paths.
Tie the choice to rotation strategy — whether a credential change is a file refresh or a rollout — and cover failure modes, optional: true, file permissions and the propagation window during rotation.
Set the platform default (volumes plus re-read-on-use for anything rotating), define the app contract for reloading, and weigh it against short-lived credentials that make static Secrets unnecessary in the first place.
## The two injection paths A Secret reaches a container in one of two ways. **As environment variables**, via `envFrom.secretRef` (all keys, names become variable names) or `env[].valueFrom.secretKeyRef` (one key, explicitly named). The kubelet reads the Secret at container-start time and passes the values in the process environment when it execs the entrypoint. **As a volume**, via `volumes[].secret` and a `volumeMounts` entry. The kubelet materialises each key as a file inside the mount directory. ## Freshness: the decisive difference Process environments are immutable after exec on Linux from the outside — nothing can change a running process's environment for it. So an env-injected Secret is a snapshot taken when that container started. Rotate the Secret and the running container keeps the old value indefinitely; it only picks up the new one after a restart, which means a rotation is a rollout. Volumes are live. The kubelet keeps the mounted content in sync with the API object. Propagation is not instant: it is bounded by the kubelet sync loop plus the object cache TTL, so plan for roughly a minute rather than milliseconds. The update is **atomic**, which matters more than people expect. The kubelet writes a new timestamped directory containing all keys, then atomically swaps the `..data` symlink to point at it. Files visible in the mount are symlinks through `..data`. Consequently a reader either sees the entire old set or the entire new set — never a TLS key from the new pair with a certificate from the old. This is why cert-manager-style rotation works safely through volumes. **`subPath` breaks all of this.** Mounting a single key with `subPath: tls.crt` bind-mounts that one file into the container, bypassing the symlink indirection. The kubelet cannot update it, so the content is fixed for the Pod's lifetime. This is the number-one surprise in production: "we mounted the cert but renewal never reached the Pod". If you need one file at a specific path and still want updates, mount the whole volume elsewhere and symlink, or use `items` to project only the keys you want into a normal (non-subPath) mount. ## Exposure surface Environment variables are broad by nature: - readable at `/proc/<pid>/environ` by anything in the same Pod's PID namespace or by root on the node; - inherited by every child process, including shells and helper binaries; - captured by core dumps and by many crash/APM reporters that snapshot the environment; - printed by frameworks and debug endpoints that dump configuration on startup; - visible in the container runtime's inspect output on the node. Volume files are narrower: one path, permissions you control with `defaultMode` (e.g. `0400`) and `fsGroup`, memory-backed so never written to node disk, and no automatic inheritance into child processes. They are still readable by anyone who can `exec` into the Pod — neither option defends against that. One small point in favour of env vars: `kubectl describe pod` shows a `secretKeyRef` as a *reference*, not a value, so the manifest itself does not leak. The leak paths are inside the container and on the node, not in the API object. ## Failure behaviour With `secretKeyRef`, a missing Secret or missing key stops the container from starting — the Pod sits in `CreateContainerConfigError` — unless you set `optional: true`. With a volume, a missing Secret blocks the mount and the Pod stays in `ContainerCreating` until the Secret appears, again unless `optional: true`. Fast-fail on a missing credential is usually what you want; the `optional` flag is for genuinely optional configuration. ## Practical guidance - Rotating material — TLS certificates and keys, database credentials under automated rotation, tokens with a lifetime — should be **volumes**, and the application should re-read on use or watch the file. Deliver a rotation without a restart, or accept that rotation equals a rollout. - Static credentials in a simple twelve-factor app, where the operational cost of a restart per change is nil, are fine as **env vars**. Prefer explicit `secretKeyRef` over `envFrom` so you can see exactly which values the container gets and avoid accidentally injecting new keys when someone adds them to the Secret. - Whatever you choose, ensure the app never logs the value and that crash handlers scrub the environment. ## A third option worth naming A `projected` volume can combine several Secrets, ConfigMaps, a Downward API entry and a short-lived service-account token into one directory with explicit paths. It is the tidy choice when a container needs several pieces of material in one place, and it keeps the atomic-update behaviour of ordinary Secret volumes.
- How quickly does a rotated Secret reach a mounted volume, and what governs the delay?On the order of a minute, not instantly. The bound is the kubelet's sync period plus the TTL of its object cache for ConfigMaps and Secrets, so the application must tolerate a window where old and new material coexist across the fleet. If you need immediate cutover, do a rollout instead and treat the volume refresh as best-effort.
- Why does mounting a Secret key with `subPath` stop it from updating?A `subPath` mount bind-mounts that single file into the container, bypassing the `..data` symlink indirection the kubelet uses to swap contents atomically. Since the kubelet updates by repointing that symlink, a bind-mounted file can never be refreshed and stays fixed for the Pod's lifetime. Mount the directory normally, or use `items` to project just the keys you need.
An environment variable is a value photocopied onto the process at birth; a volume file is a noticeboard the kubelet keeps repinning — but pin one sheet directly to the wall with subPath and nobody can replace it.
saying these in an interview costs you the question
- Believing environment variables update when the Secret changes.
- Using `subPath` for TLS material and expecting certificate renewal to reach the Pod.
- Assuming volume updates are instantaneous rather than bounded by the kubelet sync period.
- Claiming volumes are safe from anyone who can `exec` into the Pod — they are not.
- Thinking `envFrom.secretRef` is safer than `secretKeyRef`; it is broader, injecting every key including ones added later.