Rotate one existing service-owned runtime environment variable when you can issue a replacement that overlaps with the old credential long enough to verify consumers. This guide pairs with the downloadable credential-rotation sample.
Source pin: Bex 63c7582729bd604e0cac55d70634358b58f066f8 (secrets
batch.go / service.go / rest.go — expectedEnvRevision CAS). The pin
documents source behavior in that checkout; it is not evidence of the build
deployed at api.bex.co. An installation may differ — treat observed error
codes and response shapes as installation-specific when they diverge.
What this covers (and what it does not)
| In scope | Out of scope here |
|---|---|
| One ordinary env key/value on a service you manage | Database role rotation, issuer-native APIs, build-time ARG/ENV baked into images |
Revision-checked PATCH …/environment | Whole-list PUT …/env-vars replacement |
| Application-owned consumer checks you enumerate | Proof that every load-balanced replica flipped |
Explicit retirement after all_new | Restoring a revoked external credential |
Environment group values and service overrides can still supply the key your process reads — confirm which layer wins before rotating. See Environment variables & secrets.
Prerequisites
- Node.js ≥ 22.12 for the local sample.
- Manage authorization on the target service (writes) and view-sensitive for reading the selected key/revision.
- A versioned secrets store on the platform (CAS requires it).
- An external issuer (or the sample's synthetic issuer) that can leave old and new credentials valid during overlap.
Download and local rehearsal
curl -fsSL -o credential-rotation.zip \
https://bex.co/examples/credential-rotation-latest.zip
unzip credential-rotation.zip -d credential-rotation
cd credential-rotation
npm test
npm run rehearse
npm run stage -- happy
npm run scenarios -- stale-revision
npm run scenarios -- partial-activation
npm run scenarios -- premature-revoke
npm run scenarios -- interrupt-uncertainEvery local report includes "evidence":"local_fixture". The synthetic issuer
is not Bex and not your real IdP.
Supported compare-and-set shape
PATCH /v1/services/{id}/environment with expectedEnvRevision:
- Exactly one ordinary
envVars[]assignment (key + value). - No secret files, generate, delete, rename (
fromKey), or multi-key CAS. - Key must already exist (
ENVIRONMENT_VARIABLE_NOT_FOUNDotherwise). - Stale revision →
ENVIRONMENT_REVISION_CONFLICT— refresh and retry; do not overwrite another editor. - Result names keys and sets
rolledOutwhensaveModeisdeploy. That flag means a rollout was requested, not that consumers adopted the value.
Read the selected key first (GET …/env-vars/{key}) and use its opaque
revision. If the server omits a revision, stop — do not fall back to an
unprotected write.
Staged flow
- Issue a replacement at the provider (or
npm run rehearselocally). - Update Bex with the observed revision (
node update.mjsor the sample's fixture API). Prefersave_onlywhile validating, thendeploywhen you intend a rollout request. - Activate application processes so they load the new value (redeploy,
restart, or your app's reload path). Platform
rolledOutalone is not this step. - Verify every named consumer you can observe. Mixed, failed, timed-out, or incomplete results must not retire the old credential.
- Retire the old credential explicitly at the issuer only after verification reports all enumerated consumers on the new id.
save_only versus deploy
| Outcome | Meaning in the sample |
|---|---|
saved: true, rolledOut: false | Config stored; consumers may still present the old credential |
rolledOut: true | Deploy save mode completed its rollout request path |
consumerReady | Always false from the update client — readiness is a separate check |
Recovery scenarios
Run these from the extracted sample:
| Command | Expected lesson |
|---|---|
npm run scenarios -- stale-revision | Competing writer wins; recover by re-observing, never by forcing the stale token |
npm run scenarios -- partial-activation | Mixed consumers refuse retirement; keep the old credential active |
npm run scenarios -- premature-revoke | Revoked credentials stay unusable — overlap exists so you can avoid this |
npm run scenarios -- interrupt-uncertain | Ambiguous transport after submit is uncertain; re-read state; do not auto-revoke |
Failure and interruption never automatically revoke the old credential.
Hosted (reader-authorized) update
export BEX_API_ORIGIN=https://api.bex.co
export BEX_ACCESS_TOKEN=… # never commit
export BEX_SERVICE_ID=svc_…
export BEX_ENV_KEY=APP_TOKEN
export BEX_ENV_VALUE=… # replacement; never commit or log
export BEX_SAVE_MODE=deploy # or save_only
node update.mjsThen verify your consumers with application-owned checks before issuer revocation. If you cannot enumerate consumers, or the provider cannot overlap credentials, leave verification incomplete and follow that provider's procedure rather than claiming a certified zero-downtime rotation.
Related
- Environment variables & secrets — sparse patch, whole-list PUT risk, groups
- How deploys work — configuration rollout versus image deploys
- API overview — authentication and service routes