Skip to main content

Rotate an application environment credential

Rehearse issuing a replacement, applying a revision-checked environment update, verifying named consumers, and retiring the old credential without treating rolledOut as readiness.

Share
Last updated on September 23, 2026
On this page

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.goexpectedEnvRevision 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 scopeOut of scope here
One ordinary env key/value on a service you manageDatabase role rotation, issuer-native APIs, build-time ARG/ENV baked into images
Revision-checked PATCH …/environmentWhole-list PUT …/env-vars replacement
Application-owned consumer checks you enumerateProof that every load-balanced replica flipped
Explicit retirement after all_newRestoring 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

bash
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-uncertain

Every 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_FOUND otherwise).
  • Stale revision → ENVIRONMENT_REVISION_CONFLICT — refresh and retry; do not overwrite another editor.
  • Result names keys and sets rolledOut when saveMode is deploy. 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

  1. Issue a replacement at the provider (or npm run rehearse locally).
  2. Update Bex with the observed revision (node update.mjs or the sample's fixture API). Prefer save_only while validating, then deploy when you intend a rollout request.
  3. Activate application processes so they load the new value (redeploy, restart, or your app's reload path). Platform rolledOut alone is not this step.
  4. Verify every named consumer you can observe. Mixed, failed, timed-out, or incomplete results must not retire the old credential.
  5. Retire the old credential explicitly at the issuer only after verification reports all enumerated consumers on the new id.

save_only versus deploy

OutcomeMeaning in the sample
saved: true, rolledOut: falseConfig stored; consumers may still present the old credential
rolledOut: trueDeploy save mode completed its rollout request path
consumerReadyAlways false from the update client — readiness is a separate check

Recovery scenarios

Run these from the extracted sample:

CommandExpected lesson
npm run scenarios -- stale-revisionCompeting writer wins; recover by re-observing, never by forcing the stale token
npm run scenarios -- partial-activationMixed consumers refuse retirement; keep the old credential active
npm run scenarios -- premature-revokeRevoked credentials stay unusable — overlap exists so you can avoid this
npm run scenarios -- interrupt-uncertainAmbiguous 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

bash
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.mjs

Then 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.

Was this page helpful?

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex