Rotate secrets across dev, staging, and prod with Envault

Secret rotation is one of those jobs everyone agrees matters and almost nobody enjoys. A key leaks, or a quarterly policy says it's time, and suddenly you're editing five .env files by hand, pasting the new value into staging, then prod, then wondering if local dev ever got updated. Miss one and you get a 2am "staging can't connect to the database" page.

Envault is a local CLI that rotates a secret and rolls it out across environments in one command, with an audit log so you can prove it happened. It runs offline on the free tier, has no telemetry, and the installed command is rh-envault. Here's the workflow.

Install

# from source (not on public PyPI)
pip install git+https://github.com/Coding-Dev-Tools/envault.git

Python 3.10+. MIT licensed.

Rotate one secret

Give it a key name and Envault generates a fresh cryptographically random value:

# rotate the production database password
rh-envault rotate DB_PASSWORD --env prod

# rotate an API key for a specific environment
rh-envault rotate STRIPE_SECRET_KEY --env staging

# preview the new value without writing it yet
rh-envault rotate JWT_SECRET --length 64 --dry-run --show

The --dry-run --show combo is what I reach for first: it prints the generated value so I can confirm it looks right before anything on disk changes.

Envault infers the secret type

You don't tell it what kind of value to make. It reads the key name and picks a format that won't break the thing consuming it:

Key patternGenerated value
DB_PASSWORD, DATABASE_URLdatabase-safe password (no ambiguous chars)
API_KEY, STRIPE_SECRETprefixed API key
JWT_SECRET256-bit base64 secret
WEBHOOK_SECRETlong hex key
anything else32-character random string

That last point matters more than it sounds. A raw random string can land an @ or / in a URL-shaped secret and quietly break a connection string. Envault avoids that for known patterns.

Rotate everything at once

When a root credential leaks or a policy forces a bulk reset, rotate-all walks every key in an environment and regenerates each one, with a per-key dry-run preview first:

rh-envault rotate-all --env prod --dry-run
rh-envault rotate-all --env prod

Roll the new value out to every environment

Rotation updates the source environment. To push it to the others without clobbering their own secrets:

# copy the rotated value from prod into staging, keep staging's other secrets
rh-envault sync prod staging --strategy source_wins

# confirm the two environments actually match now
rh-envault diff prod staging --fail-on-missing

source_wins means the source environment's values take precedence on conflict, so the rotated value lands in the target. Keys that only exist in the target are left alone — you don't accidentally flatten an environment's unique credentials.

Prove the rotation happened

Every rotate, diff, and sync is written to .envault-audit.log. When someone asks "who changed the database password and when," you don't grep git history:

rh-envault audit --action rotate --limit 20
rh-envault audit --key DB_PASSWORD

Put rotation in CI

The cheapest way to stop drift is to fail the build before a bad deploy lands. Add a parity check that blocks promotion when production is missing a key staging has:

# .github/workflows/env-check.yml (excerpt)
- name: Check parity with staging
  run: rh-envault diff staging prod --fail-on-missing
- name: Audit before deploy
  run: rh-envault audit --action rotate --limit 20

What it costs

The rotate/audit/diff/sync loop is free and unlimited on the local CLI. Secret-store integrations (AWS SSM, HashiCorp Vault, Doppler, 1Password), unlimited audit history, team shared configs, and compliance exports unlock at paid tiers: Individual $12/mo, the 11-tool Suite $49/mo, Team $79/mo. Enterprise is custom.

Get started

pip install git+https://github.com/Coding-Dev-Tools/envault.git
rh-envault init my-project
rh-envault rotate DB_PASSWORD --env prod

MIT. No telemetry. No account required for the free tier.