A Kubernetes Secret update can succeed at the API, return a new resourceVersion, and still leave a file mounted with subPath unchanged inside a running container. That is not, by itself, evidence of failed reconciliation. Kubernetes explicitly documents two different behaviors: Secret data exposed through a normal Secret volume is updated with eventual consistency, while a container consuming that Secret through a subPath volume mount does not receive automated Secret updates.
The operational risk is the false-positive success signal. An operator patches the Secret, kubectl exits zero, the API object contains the new bytes, and a deployment gate concludes that configuration has propagated. None of those facts proves what a long-running process can read from its mounted path. Conversely, an unchanged subPath file does not prove that Kubernetes failed to observe the Secret mutation; it can be the documented outcome.
This playbook treats the problem as a release-acceptance exercise rather than a Secret tutorial. The target is narrow: prove Secret projection updates versus subPath-mounted files with one disposable fixture, synthetic values, pinned runtime versions, an independent expected-case manifest, a negative control, a raw evidence ledger, and an explicit ACCEPT / REPAIR / RERUN / HOLD decision.
No Kubernetes execution was performed for this article. Every runtime outcome below is therefore labeled EXPECTED / NOT EXECUTED unless it is a statement from Kubernetes documentation. Do not convert those expectations into observed facts until your own evidence bundle contains the corresponding commands and outputs.
Define the operational acceptance contract and pin the documented baseline
Start by separating three states that incident reports often collapse into “the Secret changed.”
The first is API state: what value does the Secret object currently contain? The Kubernetes Secret API defines data as the stored map of Secret values and describes stringData as a write-time convenience merged into data; stringData is not returned when the object is subsequently read. The same API also defines immutable: true as preventing subsequent data changes.
The second state is the normal projected Secret volume visible to a running container. Kubernetes documents that when a Secret used in a volume is updated, Kubernetes tracks the change and eventually updates the volume. The kubelet may discover the change through a watch, a TTL-based cache, or direct polling. With the default watch strategy, refresh is eventually consistent rather than synchronous with the API write.
The third state is the file mounted through volumeMounts[*].subPath. Kubernetes documents a categorical exception: a container using a Secret as a subPath volume mount does not receive automated Secret updates. The volumes documentation repeats the same rule.
That gives the acceptance contract:
Assertion | Required evidence | Expected interpretation |
Secret mutation reached the API | Secret value/hash after mutation plus resourceVersion | Control-plane write succeeded |
Normal Secret volume reaches new value | Container read from ordinary mount | Projection reconciliation succeeded |
Existing subPath file remains old | Container read from subPath mount | Documented negative control |
Recreated subPath consumer receives new value | Read after Pod recreation | Repair works at Pod lifecycle boundary |
Evidence records exact runtime | kind, Kubernetes, kubectl, image identity | Result belongs to a defined version envelope |
A zero exit code from kubectl patch satisfies only the first line. It cannot satisfy the release gate by itself.
The methodological pattern is similar to Refonte Learning's verified Kubernetes preStop shutdown-budget playbook: that page distinguishes a successful lifecycle action from evidence that the intended downstream state was reached. Its subject is Pod termination rather than Secret projection.
For versioned behavior, pin the fixture instead of testing “current Kubernetes.” As of November 10, 2026, Kubernetes maintains the 1.37, 1.36, and 1.35 minor release branches; the releases page lists 1.37.1 as the latest 1.37 patch available in its current release history. The fixture below deliberately targets Kubernetes v1.37.0, because kind v0.33.0 publishes a content-addressed v1.37.0 node image:
kind: v0.33.0
Kubernetes: v1.37.0
kubectl: v1.37.0
node image: kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5
test image: registry.k8s.io/e2e-test-images/busybox:1.36.1-1kind's v0.33.0 release notes state that it defaults to Kubernetes 1.37.0 and provide that exact node-image digest. Kubernetes itself uses registry.k8s.io/e2e-test-images/busybox:1.36.1-1 in upstream test material, making it a suitable small shell image for this synthetic fixture.
This behavior predates Kubernetes 1.37. Kubernetes v1.34 documentation already states that ordinary Secret volumes update eventually and that subPath Secret mounts do not receive automated updates. The v1.37 fixture is therefore a versioned validation target for established behavior, not evidence of a newly introduced 2026 feature.
The boundary rule is simple: if your kubectl version, server version, kind version, node-image digest, or resolved workload image differs from the approved run manifest, classify the run as RERUN under the intended version or explicitly qualify the new version before reusing the result.
Build an isolated reproducible fixture and an independent expected-case manifest
Use one disposable kind cluster, one namespace, one Secret, one Pod, and two containers. Both containers consume the same Secret from the same Pod. One mounts the volume normally; the other mounts only the token file using subPath. This controls for Secret identity, node placement, mutation time, and most environmental differences while preserving the one variable being tested: mount semantics.
Install the pinned tools using your organization's verified binary-distribution process. The canonical kind documentation currently recommends tagged releases for CI, and the v0.33.0 release supplies the node-image digest used here.
Create kind.yaml:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: secret-subpath-fixture
nodes:
- role: control-plane
image: kindest/node:v1.37.0@sha256:a1ed56cfb0e7b93589bdf97c8cd566405a265939e3620fc4f5de89adff580ae5Create the cluster and capture provenance before applying the test:
set -euo pipefail
kind version
kubectl version --client
kind create cluster --config kind.yaml
kubectl version
kubectl get nodes -o wide
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.nodeInfo.kubeletVersion}{"\t"}{.status.nodeInfo.containerRuntimeVersion}{"\n"}{end}'Now create fixture.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: secret-subpath-validation
---
apiVersion: v1
kind: Secret
metadata:
name: refresh-fixture
namespace: secret-subpath-validation
type: Opaque
stringData:
token: fixture-v1
---
apiVersion: v1
kind: Pod
metadata:
name: consumers
namespace: secret-subpath-validation
spec:
restartPolicy: Never
containers:
- name: projected
image: registry.k8s.io/e2e-test-images/busybox:1.36.1-1
command: ["sh", "-c", "trap : TERM INT; sleep 86400 & wait"]
volumeMounts:
- name: secret-data
mountPath: /secret
readOnly: true
- name: subpath
image: registry.k8s.io/e2e-test-images/busybox:1.36.1-1
command: ["sh", "-c", "trap : TERM INT; sleep 86400 & wait"]
volumeMounts:
- name: secret-data
mountPath: /mounted/token
subPath: token
readOnly: true
volumes:
- name: secret-data
secret:
secretName: refresh-fixtureApply it:
kubectl apply -f fixture.yaml
kubectl -n secret-subpath-validation wait \
--for=condition=Ready pod/consumers \
--timeout=120sThe container image tag is version-specific but is not content-addressed in this article. Therefore capture the runtime-resolved image IDs and treat them as part of the evidence:
kubectl -n secret-subpath-validation get pod consumers \
-o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.image}{"\t"}{.imageID}{"\n"}{end}'If a later rerun resolves a different image ID, do not silently merge its observations into the original evidence bundle.
The fixture itself must not be the source of truth for expected results. Maintain a separate expected.yaml, reviewed before mutation:
schemaVersion: 1
fixture: kubernetes-secret-subpath-refresh
runtime:
kind: v0.33.0
kubernetes: v1.37.0
kubectl: v1.37.0
syntheticValues:
initial:
plaintext: fixture-v1
sha256: fc1c0e0c7ae8690bf702ade724d2543f81603a03b3956338f76ddef54ad713b9
updated:
plaintext: fixture-v2
sha256: 11397486b51b31d1165d5bbea76aee8684c0ef62d3828616df5f9d91fc59cf9e
cases:
baseline_api:
expected: fixture-v1
status: EXPECTED_NOT_EXECUTED
baseline_projected:
expected: fixture-v1
status: EXPECTED_NOT_EXECUTED
baseline_subpath:
expected: fixture-v1
status: EXPECTED_NOT_EXECUTED
after_update_api:
expected: fixture-v2
status: EXPECTED_NOT_EXECUTED
after_update_projected:
expected: fixture-v2
consistency: eventual
status: EXPECTED_NOT_EXECUTED
after_update_existing_subpath:
expected: fixture-v1
role: negative_control
status: EXPECTED_NOT_EXECUTED
after_recreate_subpath:
expected: fixture-v2
status: EXPECTED_NOT_EXECUTEDThe SHA-256 values above are deterministic hashes of the literal UTF-8 strings without trailing newline. Verify them independently before use:
printf '%s' 'fixture-v1' | sha256sum
printf '%s' 'fixture-v2' | sha256sumThe existing subPath mount is the negative control. Its purpose is not to “fail” the test. It should remain on fixture-v1 after the Secret changes to fixture-v2, because that is the documented behavior under test. If both consumers automatically become fixture-v2, the test has failed to preserve the expected failure boundary and must be classified HOLD pending investigation.
The pattern is intentionally analogous to the independent state contract in Refonte's SQLite WAL committed-row validation: that article refuses to infer recovery correctness merely because a database file opens. Here, similarly, API mutation success is not allowed to stand in for mounted-file state.
Reproduce the misleading success and run the controlled comparison
First establish the baseline. Do not mutate the Secret until all three reads agree on fixture-v1.
NS=secret-subpath-validation
POD=consumers
printf 'API baseline: '
kubectl -n "$NS" get secret refresh-fixture \
-o jsonpath='{.data.token}' | base64 --decode
printf '\n'
printf 'Projected baseline: '
kubectl -n "$NS" exec "$POD" -c projected -- cat /secret/token
printf '\n'
printf 'subPath baseline: '
kubectl -n "$NS" exec "$POD" -c subpath -- cat /mounted/token
printf '\n'Expected / not executed: all three return fixture-v1.
Capture the Secret identity and resourceVersion before mutation:
kubectl -n "$NS" get secret refresh-fixture \
-o jsonpath='{.metadata.uid}{"\t"}{.metadata.resourceVersion}{"\n"}'Now reproduce the apparent success signal:
kubectl -n "$NS" patch secret refresh-fixture \
--type=merge \
-p '{"stringData":{"token":"fixture-v2"}}'Immediately query API state separately:
kubectl -n "$NS" get secret refresh-fixture \
-o jsonpath='{.metadata.resourceVersion}{"\t"}{.data.token}{"\n"}'Decode the value:
kubectl -n "$NS" get secret refresh-fixture \
-o jsonpath='{.data.token}' | base64 --decode
printf '\n'Expected / not executed: the API read returns fixture-v2, demonstrating that the control-plane object changed. The Secret API documents that stringData is merged into data for writes and is not emitted when the Secret is later read.
This is where weak validation typically stops. The patch command succeeded and the object looks correct. That evidence is necessary but insufficient.
The ordinary volume is an eventually consistent projection, so do not require instantaneous equality. Kubernetes states that the delay can include kubelet synchronization plus cache-propagation delay; the change-detection mechanism is configurable and defaults to watch. Use a bounded polling loop and preserve every attempt:
deadline=$((SECONDS + 180))
attempt=0
while :; do
attempt=$((attempt + 1))
value="$(kubectl -n "$NS" exec "$POD" -c projected -- cat /secret/token)"
printf '%s projected attempt=%d value=%s\n' \
"$(date -u +%FT%TZ)" "$attempt" "$value"
if [ "$value" = "fixture-v2" ]; then
break
fi
if [ "$SECONDS" -ge "$deadline" ]; then
printf 'PROJECTED_REFRESH_TIMEOUT\n' >&2
exit 40
fi
sleep 2
doneThe 180-second timeout is a fixture policy, not a Kubernetes guarantee. If it expires, classify the projection case HOLD, not “Kubernetes violates its documentation.” A timeout can reflect environment, kubelet configuration, unavailable components, or a test harness problem and requires further evidence.
Next read the negative control:
kubectl -n "$NS" exec "$POD" -c subpath -- cat /mounted/token
printf '\n'Expected / not executed: the existing subPath mount still returns fixture-v1. Kubernetes documents exactly that a Secret used as a subPath mount does not receive automated Secret updates.
Now the controlled comparison has four distinct predicates:
API object -> fixture-v2
ordinary Secret projection -> fixture-v2 eventually
existing subPath file -> fixture-v1
Pod identity -> unchangedOnly the conjunction supports the diagnosis “Secret update reconciled normally; stale subPath content is the documented mount behavior.”
Do not replace the last predicate with inode inspection alone. Filesystem metadata can be useful supporting evidence, but the acceptance contract concerns bytes readable by the process. The strongest observation is therefore a direct read from each application-visible path.
This evidentiary separation resembles the task-versus-executor distinction in Refonte's Java periodic-task validation: that playbook does not infer a task's state from aggregate executor health. Here the analogous discipline is not to infer mounted-file state from API write health. The Java playbook examines scheduler behavior rather than Kubernetes configuration.
The rejection criteria are stricter than “something changed.” HOLD the run if the API remains at fixture-v1, because the intended mutation never happened. HOLD if the ordinary projection cannot be shown to reach fixture-v2 within the declared laboratory timeout. HOLD if the subPath file changes automatically while the same Pod remains running, because the observed result contradicts the documented negative control and the fixture or environment must be examined before a release claim is made.
Test boundary cases and capture raw operational evidence
A controlled happy path is insufficient for release acceptance. Test the boundaries that distinguish an actionable diagnosis from an accidental green result.
The first boundary is an immutable Secret. The Kubernetes Secret API defines immutable: true as preventing modification of the Secret's data. Create a separate object so the main fixture remains uncontaminated:
apiVersion: v1
kind: Secret
metadata:
name: immutable-control
namespace: secret-subpath-validation
type: Opaque
immutable: true
stringData:
token: fixture-v1Apply and attempt the same mutation:
kubectl apply -f immutable-secret.yaml
set +e
kubectl -n secret-subpath-validation patch secret immutable-control \
--type=merge \
-p '{"stringData":{"token":"fixture-v2"}}' \
>immutable-patch.stdout 2>immutable-patch.stderr
rc=$?
set -e
printf 'rc=%d\n' "$rc"
cat immutable-patch.stdout
cat immutable-patch.stderrExpected / not executed: the mutation is rejected rather than accepted. The important assertion is not the exact wording of the error, which can vary, but that the API value remains fixture-v1 and the command is not treated as a successful mutation. This control protects against a misleading analysis in which stale mounted content is blamed on subPath even though no valid Secret update occurred.
The second boundary is the ConfigMap analogue. Kubernetes documents the same pattern for ConfigMaps. Mounted ConfigMap data is eventually updated, while a ConfigMap used through subPath does not receive updates. That consistency strengthens the interpretation that this is a volume-mount lifecycle rule rather than a peculiarity of Secret encoding.
The third boundary is evidence completeness. Treat missing evidence as a state, not as permission to infer.
Before running, create an evidence directory:
RUN_ID="$(date -u +%Y%m%dT%H%M%SZ)"
EVIDENCE="evidence/$RUN_ID"
mkdir -p "$EVIDENCE"Capture version provenance:
kind version >"$EVIDENCE/kind-version.txt" 2>&1
kubectl version >"$EVIDENCE/kubectl-version.txt" 2>&1
kubectl get nodes -o wide >"$EVIDENCE/nodes.txt"
kubectl get pod -n secret-subpath-validation consumers -o yaml \
>"$EVIDENCE/pod.yaml"
kubectl get secret -n secret-subpath-validation refresh-fixture -o yaml \
>"$EVIDENCE/secret-current.yaml"Because even synthetic Secret values can create poor operational habits, production evidence should usually retain hashes or redacted comparisons rather than plaintext credentials. This fixture deliberately uses non-sensitive synthetic strings so exact values can be inspected safely.
Record commands and outputs as separate evidence events:
cdapture() {
name="$1"
shift
{
printf 'timestamp=%s\n' "$(date -u +%FT%TZ)"
printf 'command='
printf '%q ' "$@"
printf '\n'
"$@"
rc=$?
printf '\nexit_code=%d\n' "$rc"
exit "$rc"
} >"$EVIDENCE/$name.txt" 2>&1
}Do not use capture carelessly around pipelines unless the shell has set -o pipefail; otherwise the recorded exit status can belong only to the final command.
The minimum evidence ledger should look like this before any release decision:
Evidence ID | State being proved | Source | Required |
E01 | kind version | kind version | Yes |
E02 | client/server Kubernetes versions | kubectl version | Yes |
E03 | node/kubelet identity | node API | Yes |
E04 | Pod UID and image IDs | Pod API | Yes |
E05 | initial Secret API value/hash | Secret API | Yes |
E06 | initial ordinary mount value/hash | container read | Yes |
E07 | initial subPath value/hash | container read | Yes |
E08 | post-patch Secret API value/hash | Secret API | Yes |
E09 | ordinary projection reaches v2 | bounded container polling | Yes |
E10 | existing subPath remains v1 | container read | Yes |
E11 | recreated Pod subPath reaches v2 | container read | Required for repair acceptance |
E12 | rollback API and consumer states | API + container reads | Required if rollback is tested |
If E08 is present but E09 and E10 are absent, the run proves an API mutation only. It does not prove Secret projection behavior. If E10 exists but there is no Pod UID evidence, you cannot rule out that the Pod was recreated between observations. If E09 exists but the expected-case manifest was written after the run, the result is weaker because the supposed expectation may simply mirror what was observed.
That independent-ledger discipline aligns with Refonte's pandas missing-key reconciliation playbook, which compares output against an independently defined source population instead of trusting an aggregate total. The reusable idea is reconciliation against independently declared expectations.
The missing-evidence rule is deliberately unforgiving: if an assertion is needed for the gate and its raw evidence is absent, classify it HOLD or RERUN. Do not reconstruct a production acceptance record from memory, screenshots of green commands, or a later cluster state.
Reconcile actual state, separate documentation from observation, and measure the repair boundary
A useful incident ledger distinguishes four epistemic categories.
Documented behavior comes from the Kubernetes contract. Ordinary Secret volume contents are updated with eventual consistency; Secret files mounted through subPath do not receive automated Secret updates. The Secret API separately defines how data, stringData, and immutability behave.
Observation is something your fixture actually recorded: for example, “E09 shows /secret/token returned hash 113974…cf9e at 14:06:12Z.” This article contains no such observations because the fixture was not executed.
Inference is the interpretation connecting observations to the contract. If a real run established API=v2, normal mount=v2, unchanged Pod UID, and subPath=v1, a reasonable inference would be that Kubernetes reconciled the Secret volume as documented and the old subPath file is the documented exception rather than evidence that the Secret controller missed the change.
Expected result is a predeclared outcome awaiting execution. Every fixture case in this article belongs here.
Use a reconciliation table rather than prose alone:
Case | Independent expectation | Article status | Release meaning |
API before mutation | fixture-v1 | EXPECTED / NOT EXECUTED | Baseline |
normal mount before mutation | fixture-v1 | EXPECTED / NOT EXECUTED | Baseline |
subPath before mutation | fixture-v1 | EXPECTED / NOT EXECUTED | Baseline |
API after mutation | fixture-v2 | EXPECTED / NOT EXECUTED | Mutation succeeded |
normal mount after reconciliation | fixture-v2 | EXPECTED / NOT EXECUTED | Projection refreshed |
existing subPath after mutation | fixture-v1 | EXPECTED / NOT EXECUTED | Negative control |
recreated subPath consumer | fixture-v2 | EXPECTED / NOT EXECUTED | Repair boundary |
immutable control mutation | rejected | EXPECTED / NOT EXECUTED | API failure boundary |
The repair boundary is Pod recreation, not an arbitrary sleep interval. Waiting longer is appropriate for the ordinary projected volume because Kubernetes explicitly describes its update as eventually consistent. Waiting indefinitely for an existing Secret subPath mount to refresh contradicts the documented contract; the container does not receive automated Secret updates through that mount.
Measure the repair explicitly. First preserve the Pod UID:
NS=secret-subpath-validation
old_uid="$(
kubectl -n "$NS" get pod consumers \
-o jsonpath='{.metadata.uid}'
)"
printf 'old_uid=%s\n' "$old_uid"Delete and recreate only the Pod, leaving the Secret at fixture-v2:
kubectl -n "$NS" delete pod consumers --wait=true
kubectl apply -f pod-only.yaml
kubectl -n "$NS" wait \
--for=condition=Ready pod/consumers \
--timeout=120spod-only.yaml should contain exactly the Pod portion of the original fixture and must not reapply the original fixture-v1 Secret.
Capture the new UID:
new_uid="$(
kubectl -n "$NS" get pod consumers \
-o jsonpath='{.metadata.uid}'
)"
printf 'new_uid=%s\n' "$new_uid"
test "$old_uid" != "$new_uid"Now read both paths:
kubectl -n "$NS" exec consumers -c projected -- cat /secret/token
printf '\n'
kubectl -n "$NS" exec consumers -c subpath -- cat /mounted/token
printf '\n'Expected / not executed: both now return fixture-v2.
This step proves a much narrower statement than “restart fixes Secrets.” It tests that a newly created consumer binds to the current Secret data in this fixture. It does not prove that restarting is the right application-level migration mechanism for every workload, nor that an application can safely reload credentials merely because the file contains new bytes.
The distinction matters operationally. If the application relies on a single-file subPath mount because it cannot tolerate the directory structure of a Secret volume, the platform design has encoded a refresh limitation. The repair may therefore be to recreate Pods whenever the Secret changes, or to redesign the mount/application interface so the process consumes a normal Secret volume and implements a safe reload policy. Which option is acceptable belongs to the workload's release contract, not to Kubernetes documentation.
Limitations of this fixture must remain attached to the evidence. It uses one-node kind rather than a production topology; one Secret key; one Linux-oriented shell test image; an Opaque Secret; and a file read rather than an application-specific credential reload. It does not test CSI Secret providers, external secret operators, environment-variable Secret consumption, immutable workloads, Windows nodes, service-account token projection, or application caching. Those are outside scope and should not be inferred from this result.
Apply the smallest safe repair and prove rollback and rerun safety
The smallest safe repair depends on what the application contract requires.
If configuration must update in a running Pod without recreation, an existing subPath Secret mount does not satisfy that requirement under the documented Kubernetes behavior. The structural repair is to stop relying on subPath for a file that must receive automated Secret projection updates, mount the Secret normally, and have the application read from that projected location or from an application-controlled indirection that you independently validate.
If Pod recreation is acceptable, the smaller operational repair may be to make Secret rotation trigger a controlled rollout. The acceptance test is then not “Secret changed,” but:
new Secret API state
AND new Pod identity
AND new mounted-file state
AND application-level acceptanceThis article deliberately stops before prescribing a controller, annotation convention, checksum strategy, or third-party reloader because those mechanisms introduce a different contract. Whatever initiates the rollout must still produce state evidence from the new Pod.
Before modifying production manifests, preserve the original specification and current Secret metadata. For the synthetic fixture:
kubectl -n secret-subpath-validation get pod consumers -o yaml \
>before-repair-pod.yaml
kubectl -n secret-subpath-validation get secret refresh-fixture -o yaml \
>before-repair-secret.yamlDo not copy this pattern mechanically for real credentials into unrestricted evidence storage. Production Secret backups require access control appropriate to the secret material.
Rollback tests should reverse the same state transition rather than inventing a separate path. After the repaired/recreated Pod is confirmed at fixture-v2, patch the Secret back to fixture-v1:
kubectl -n secret-subpath-validation patch secret refresh-fixture \
--type=merge \
-p '{"stringData":{"token":"fixture-v1"}}'Expected / not executed: the ordinary projected volume eventually returns fixture-v1; the already-created subPath file remains fixture-v2 until that Pod is replaced. That expectation follows from the same documented distinction used for the forward transition.
If the approved operating model is “recreate Pods on Secret rotation,” complete rollback by recreating the Pod and then require both consumers to read fixture-v1.
Rollback evidence must answer four questions: Did the API state return to the intended value? Did the normal projection reconcile? Did the subPath negative control remain pinned until recreation? Did the replacement Pod receive the rollback value? A rollback command exiting zero is not sufficient.
Rerun safety matters because a diagnostic fixture that depends on leftovers is not reproducible. Clean the namespace completely:
kubectl delete namespace secret-subpath-validation --wait=trueThen verify absence:
kubectl get namespace secret-subpath-validationThe second command should fail because the namespace no longer exists. Reapply the fixture from source and require the baseline to start from fixture-v1; if it starts from fixture-v2, the fixture is contaminated or your files differ from the reviewed manifest.
You can also destroy the entire cluster:
kind delete cluster --name secret-subpath-fixture
kind get clustersFor a qualification run, prefer a fresh disposable cluster when practical. It narrows ambiguity about previous mutations, admission changes, or kubelet state.
The ownership model should be explicit. The platform owner owns Kubernetes/runtime provenance and whether the mount pattern matches the intended semantics. The application owner owns the requirement for live refresh versus restart and whether reading new file bytes is sufficient for the application to adopt a credential. The release owner owns the acceptance evidence and final gate. The security owner, where required, owns handling of actual secret material and evidence retention. None of those roles should silently substitute API success for application-visible state.
Refonte's Ansible check-mode validation is a useful adjacent example of the same release discipline: a green high-level result can hide unvalidated state, so the article records what actually executed and what was only predicted. That playbook examines infrastructure-as-code dry runs.
Make the ACCEPT, REPAIR, RERUN, or HOLD release decision
The gate should be mechanical enough that two experienced engineers reviewing the same ledger reach the same disposition.
Use ACCEPT only when the deployed behavior matches the declared workload contract and every mandatory evidence item exists. For a workload intentionally using ordinary Secret volume projection, that means the API shows the intended Secret, the running consumer reaches the intended projected value within the laboratory or production acceptance policy, runtime provenance is captured, and application-level validation, if required, is green. For a workload intentionally using subPath with a restart-on-rotation contract, acceptance additionally requires evidence that the rotation caused the required Pod replacement and that the new Pod sees the new Secret.
Use REPAIR when the evidence is sound but the manifest or rollout design cannot satisfy the required behavior. The canonical case is a requirement for automatic file refresh in a long-running Pod combined with a Secret file mounted through subPath. Kubernetes explicitly says that combination will not receive automated Secret updates. The repair is therefore architectural or lifecycle-oriented, not “wait longer.”
Use RERUN when the fixture or evidence process is invalid but there is no basis yet for blaming the product behavior: wrong Kubernetes version, unpinned node image, stale namespace, missing baseline, changed expected manifest, test Pod silently recreated, incomplete logs, or a resolved workload image outside the qualified identity. Correct the test and run it again from a clean boundary.
Use HOLD when required evidence is missing or contradictory, or when a valid fixture produces behavior that conflicts with the documented contract. Examples include API=v2 but no evidence from the normal projected volume; a supposed stale subPath observation without Pod UID continuity; ordinary projection failing to reconcile within the declared test boundary; or an existing subPath mount unexpectedly refreshing while the same Pod remains running. A HOLD is not a claim that Kubernetes is defective. It means the release cannot be accepted from the available evidence.
A concise gate record can be stored as YAML:
decision:
status: HOLD
reason: article_fixture_not_executed
cases:
api_update:
state: EXPECTED_NOT_EXECUTED
projected_refresh:
state: EXPECTED_NOT_EXECUTED
subpath_negative_control:
state: EXPECTED_NOT_EXECUTED
pod_recreation_repair:
state: EXPECTED_NOT_EXECUTED
rollback:
state: EXPECTED_NOT_EXECUTED
requiredBeforeAcceptance:
- runtime provenance
- baseline evidence
- API mutation evidence
- ordinary projection evidence
- subPath negative-control evidence
- repair evidence if restart-based contract appliesThat is the correct decision for the article itself: HOLD, because no runtime experiment was executed. The documentation establishes the expected behavior, but documentation is not a substitute for observation when qualifying your own runtime, manifest, image, and release process.
The primary-source baseline is unusually clear. The Kubernetes Secrets documentation states both the eventually consistent ordinary-volume update behavior and the subPath exception. The volumes documentation independently repeats that Secret subPath mounts do not receive updates and defines subPath as mounting a sub-path of the referenced volume. The ConfigMap documentation describes the analogous refresh-versus-subPath distinction. The Secret v1 API reference defines the object fields used by the fixture, including data, stringData, and immutable. Together, those sources establish the documented contract; your evidence ledger establishes whether your deployment matches it.
Develop your DevOps validation practice
Refonte Learning's current DevOps Engineering program page describes a three-month program with a stated dedication of 12–14 hours per week. It lists Docker and Kubernetes, CI/CD, Terraform, cloud platforms, monitoring and logging among the competencies, states a requirement of being engaged in bachelor's or postgraduate studies, and describes internship availability as a potential internship rather than an unconditional guarantee.
For engineers who want a structured DevOps curriculum around the broader tooling in which this kind of release validation sits, the Refonte Learning DevOps Engineering Program is the relevant catalog entry. This playbook remains narrower: it is an operational validation procedure for one Kubernetes behavior, not a substitute for a complete DevOps curriculum.
The evidence model is not “the patch succeeded.” It is control-plane state reconciled against application-visible mount state, with an existing subPath consumer retained as a negative control and Pod recreation tested as the repair boundary.
That is the operational rule worth carrying into release review: when a Secret changes, validate each state boundary independently. A new API object version proves the write. A refreshed ordinary volume proves projection reconciliation. An old file on an unchanged subPath mount is the documented negative control. A newly created consumer reading the new bytes proves the lifecycle repair. Only the complete ledger supports ACCEPT; anything less belongs in REPAIR, RERUN, or HOLD.
