How do you wire Kubernetes Secrets into a Spring Boot app using config trees, and why is that preferable to putting secrets in committed config or a ConfigMap?
answer
- Secret volume -> one file per key -> configtree:/etc/secrets/
- value stays in cluster, only a path in the app
- ConfigMap = plaintext/non-sensitive; Secret = RBAC + encrypt-at-rest
- files hot-reload; env vars leak + need restart
- tree read at startup — rotation needs /refresh, not automatic
basics
~20 sMount the Secret as a volume so each key becomes a file, then spring.config.import=optional:configtree:/etc/secrets/. Spring reads the files as properties. Secrets stay out of the image, out of git, and out of ConfigMaps (which aren't encrypted).
solid answer
~40 sIn Kubernetes you mount the `Secret` as a projected volume at, say, `/etc/secrets/`; each secret key becomes a file whose contents is the value. The app declares `spring.config.import=optional:configtree:/etc/secrets/`, and Boot's `ConfigTreePropertySource` exposes each file as a property (path→key, contents→value). This keeps secrets out of the container image and out of source control — the manifest references a `Secret` by name, not its value. It's preferable to a `ConfigMap` because Secrets are the intended (RBAC-controlled, optionally encrypted-at-rest) place for sensitive data, and preferable to env vars because file mounts can be **hot-reloaded** by the kubelet when the Secret changes, and don't leak into `/proc/<pid>/environ` or child-process environments. Use `optional:` so the same build runs locally without the mount, and align filenames with your `@ConfigurationProperties` prefixes.
code
java · 17 lines// application.properties
// spring.config.import=optional:configtree:/etc/secrets/
//
// Files projected by the mounted Secret:
// /etc/secrets/spring.datasource.username
// /etc/secrets/spring.datasource.password
// bind straight into Boot's datasource auto-config — no code needed.
//
// For an app-owned secret, bind explicitly and validate:
import jakarta.validation.constraints.NotBlank;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@Validated
@ConfigurationProperties("app.api")
public record ApiProps(@NotBlank String token) { }
// file /etc/secrets/app.api.token -> app.api.token, fails fast if empty/missinggo deeper
Know secrets should be mounted as files and imported via configtree:, not committed to git.
Wire the volume mount + import and explain Secret vs ConfigMap.
Compare file mounts vs env vars (leakage, reload), and explain rotation limits and RBAC/readOnly hardening.
Design the secret-delivery contract: external secret operators, encryption-at-rest, rotation strategy, and how/if the app re-reads on change.
## The end-to-end wiring 1. **Create the Secret** (values base64-encoded in the manifest, or via an external secrets operator): ```yaml apiVersion: v1 kind: Secret metadata: { name: db-credentials } stringData: spring.datasource.username: appuser spring.datasource.password: s3cr3t ``` 2. **Mount it as a volume** so each key is projected as a file: ```yaml volumes: - name: db-creds secret: { secretName: db-credentials } containers: - name: app volumeMounts: - name: db-creds mountPath: /etc/secrets readOnly: true ``` This yields `/etc/secrets/spring.datasource.username` and `/etc/secrets/spring.datasource.password`. 3. **Import in Spring**: ```properties spring.config.import=optional:configtree:/etc/secrets/ ``` Boot registers a `ConfigTreePropertySource`; the two files become `spring.datasource.username`/`spring.datasource.password`, which Boot's standard datasource auto-config binds — no code changes. ## Why config tree beats the alternatives - **vs. committed config / baked into the image**: secrets in `application.yml` or the Docker image leak through git history and image layers. Config trees keep the value entirely in the cluster's Secret object; the app only knows a *path*. - **vs. ConfigMap**: ConfigMaps are for **non-sensitive** data — stored in plaintext in etcd and not subject to Secret-specific handling. Secrets support encryption-at-rest and tighter RBAC. Consuming a Secret via a tree is the same mechanism; only the object kind differs. - **vs. environment variables (`SPRING_DATASOURCE_PASSWORD`)**: env vars work (relaxed binding maps them), but they are visible in `/proc/<pid>/environ`, inherited by child processes, often dumped in crash logs, and **cannot hot-reload** — changing the Secret requires a pod restart. File mounts are updated in place by the kubelet (subject to sync period + symlink swap), so a `@RefreshScope`/`Environment` re-read can pick up rotation without a restart. ## Rotation and reload nuance Kubernetes updates mounted Secret files atomically via a symlink swap, but Spring's `Environment` is read at startup — it does **not** auto-repopulate. To act on rotation you need Spring Cloud Kubernetes reload, an actuator `/refresh` + `@RefreshScope`, or an app-level watcher. Don't claim config trees give automatic live rebinding by themselves. ## Gotchas - Always `optional:` so local/dev/test without the mount doesn't fail startup. - Filenames are the property keys — a key like `db_password` won't bind to `db.password`; name files to match your `@ConfigurationProperties` prefix (dots in filenames are allowed). - `subPath` mounts do **not** receive live updates — use a plain volume mount if you want rotation. - Trailing newline: values are trimmed of a single trailing `\n` for String binds, which matters if a secret must be byte-exact (bind to `byte[]`). - Least privilege: mount `readOnly: true` and scope RBAC so only this workload can read the Secret.
- If the Kubernetes Secret is rotated, does the running app automatically pick up the new value from the config tree?No. The kubelet updates the mounted file, but Spring's Environment is read at startup. You need Spring Cloud Kubernetes reload, actuator /refresh with @RefreshScope, or a custom watcher to re-read it.
- Why choose a Secret volume over a ConfigMap for the same config-tree mechanism?Both mount as files consumed identically by configtree:, but Secrets are the intended home for sensitive data — RBAC-scoped, redacted in tooling, and encryptable at rest — whereas ConfigMaps store plaintext meant for non-sensitive config.
saying these in an interview costs you the question
- Claiming config trees auto-reload secrets into the running Spring Environment without any refresh mechanism
- Putting secrets in a ConfigMap and calling it secure
- Preferring env-var injection for secrets while ignoring their exposure in /proc and child processes
- Using a subPath mount and expecting rotation to work