skip to content

Why do hardened Kubernetes deployments prefer mounting a Secret as a file through a volume rather than injecting it into the container as an environment variable with secretKeyRef, and how does that choice affect what happens when the Secret's value is later rotated?

level: middleimportance: should knowfreq 55%

answer

  1. /proc/<pid>/environ + child inherit
  2. crash reporters dump env
  3. env resolved once at container start
  4. kubelet refreshes volume ~1 min, atomic ..data symlink
  5. subPath mounts never update

basics

~20 s

Environment variables leak: they are visible in /proc/<pid>/environ, inherited by every child process, and captured by crash reporters and debug endpoints. They are also frozen at container start, so a rotated Secret needs a Pod restart. Mounted files stay off the process environment and the kubelet refreshes them in place.

solid answer

~50 s

Two reasons, leakage and rotation. **Leakage.** An environment variable is part of the process image. Anything that can read `/proc/<pid>/environ` sees it, every child process inherits it, and error-tracking SDKs, crash dumps, and `/debug` or `/env`-style endpoints routinely dump the whole environment into third-party systems. A file mounted at a path is read deliberately by the code that needs it, is scoped by file permissions, and lives on a tmpfs the kubelet manages. **Rotation.** `env` and `envFrom` values are resolved once, when the container starts, and never change afterwards, so rotating the Secret does nothing until you restart the Pod. A Secret mounted as a volume is refreshed by the kubelet within roughly its sync period, about a minute by default, and the application can re-read the file or watch it. The exception is a `subPath` mount, which is never updated. So: files for anything long-lived or rotated; env vars only for values you accept freezing and exposing.

code

yaml · 21 lines
yaml
spec:
  containers:
    - name: app
      image: app:1.0
      # frozen at start, visible in the process environment
      env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-creds
              key: password
      # refreshed in place by the kubelet, read on demand
      volumeMounts:
        - name: db
          mountPath: /etc/secrets/db
          readOnly: true
  volumes:
    - name: db
      secret:
        secretName: db-creds
        defaultMode: 0400

go deeper

for a junior

Know the two delivery shapes and the headline rule: env vars are frozen at container start and easy to leak; mounted files can be refreshed and are read deliberately.

for a middle

Explain the concrete leak paths (/proc/<pid>/environ, inherited children, crash reporters), the kubelet's roughly one-minute atomic refresh, and the subPath exception.

for a senior

Add the operational pattern: checksum annotations to force rollouts, application-side reload strategy, and when accepting env vars for third-party images is a reasonable tradeoff.

for a principal

Set the platform default (files, no subPath, rotation-aware apps) and decide where an entrypoint shim or a sidecar is worth mandating versus where env vars are tolerated, based on credential lifetime and blast radius.

## Two delivery mechanisms A Secret reaches a container in one of two shapes. Either the Pod spec references keys through `env[].valueFrom.secretKeyRef` (or pulls a whole Secret with `envFrom.secretRef`), in which case the kubelet resolves the values at container creation and passes them in the process environment; or the Pod declares a `secret` volume (or a `projected` volume containing one) and the kubelet materialises each key as a file under the mount path, on a tmpfs. ## Why the environment is a poor place for a credential The environment block is part of the process image, and the operating system offers several ways to read it: - `/proc/<pid>/environ` exposes it to any process in the container that can see that PID, which includes sidecars sharing a process namespace and anything running as root in the container. - Every child process inherits the environment by default, so a shell-out, a subprocess, or a plugin all receive the credential whether or not they need it. - Runtime introspection surfaces dump it: crash handlers and error-reporting SDKs commonly attach the environment to reports sent to an external SaaS; framework debug endpoints (`/actuator/env`, `/debug/vars`-style handlers, phpinfo pages) print it; a stack trace including the environment ends up in the log pipeline, which usually has much broader read access than the `secrets` RBAC resource. - `kubectl describe pod` prints literal `env` values in full. It only redacts to a reference when the value comes from `secretKeyRef`, so a copy-pasted literal is fully visible to anyone with `get pods`. A file has none of these properties. It is read by the one code path that opens it, its exposure is bounded by file mode and the container's user, and it is not implicitly propagated to children. ## Rotation behaviour: the difference that surprises people Environment variables are resolved exactly once, at container start. Updating the Secret afterwards has no effect on running containers: the process environment is immutable from the outside. The only way to pick up a new value is to recreate the Pod, which teams usually automate by stamping a checksum of the Secret into a Pod-template annotation so a change rolls the Deployment. Volume-mounted Secrets are different. The kubelet keeps mounted Secrets fresh; with the default watch-based change-detection strategy the file content is replaced within roughly the sync period, on the order of a minute. The update is atomic: the kubelet writes a new timestamped directory and swings the `..data` symlink, so a reader never sees a half-written file. The application still has to notice: either re-read the file per use, watch it with inotify, or reload on a signal. Nothing about a mounted Secret makes a long-lived database connection pick up a new password by itself. One sharp edge: a volume mounted with `subPath` is resolved at mount time and is *never* refreshed. Using `subPath` to drop a single file next to existing files in a directory silently converts a rotating Secret back into a frozen one. ## When environment variables are still acceptable They are not forbidden. Twelve-factor-style apps and many third-party images only accept configuration through the environment, and rewriting them is not always worth it. The pragmatic rule: if a value is short-lived, low-value, or you were going to restart on rotation anyway, `secretKeyRef` is fine. For long-lived, high-value credentials, and especially for anything an external secret manager rotates on a schedule, mount a file. If an image insists on an environment variable, an entrypoint wrapper that reads the file and execs the real process keeps the value out of the manifest and out of the parent environment for less time. ## What to say in an interview Lead with the two concrete failure modes: environment capture (`/proc/<pid>/environ`, child processes, crash reporters, debug endpoints) and rotation freeze. Then mention the kubelet's atomic in-place refresh, the `subPath` exception, and the fact that the application must still re-read the file for a new value to take effect.

  • A Secret is mounted as a volume and you rotate the value. Nothing in the running application changes. What is going on?
    Most likely the application read the file once at startup and cached the value, so the refreshed file on disk is simply not being reread. The other common cause is a `subPath` mount, which the kubelet resolves at mount time and never updates. Fix it by removing `subPath`, and by having the app reload on file change, on a signal, or per use.
  • How do teams make a Deployment restart automatically when a Secret it consumes changes?
    They stamp a hash of the Secret's content into an annotation on the Pod template, typically generated by Helm or Kustomize at render time. Changing the Secret changes the annotation, which changes the Pod template hash and triggers a normal rolling update. Reloader-style controllers automate the same thing by watching the Secret and patching the annotation.

An environment variable is shouting the password across the room when the process starts; a mounted file is leaving it in a drawer that only the code that needs it opens.

saying these in an interview costs you the question

  • Claiming environment variables update when the Secret changes
  • Assuming a mounted Secret file update automatically reconfigures the running application
  • Using subPath for a Secret that is expected to rotate
  • Saying env vars are safe because 'only the container can see them', ignoring child processes, crash reporters, and debug endpoints

context