Tutorial

Sync and Secure Environment Variables Across Environments

Stop copy-pasting .env files. Diff, sync, and rotate environment variables across dev, staging, and prod from a single CLI — with secret store integrations, smart rotation, and CI/CD pipeline support.
May 15, 2026 · 7 min read · Revenue Holdings
Share this article:

Environment variable management is one of those problems every team has and nobody solves properly. Keys pile up across .env files. Secrets get rotated in prod but not staging. Someone pushes a .env.prod to GitHub by accident. A deployment breaks because STAGING_DB_URL was still pointing to a read replica that was decommissioned last month.

Envault is a CLI tool that brings order to your environment variables. It diffs environments, syncs with conflict resolution, rotates secrets with smart type inference, and integrates with AWS SSM, HashiCorp Vault, Doppler, and 1Password.

┌──────────┐ ┌─────────────┐ ┌───────────────┐ │ .env.dev │ │ │ │ .env.prod │ │ DB_HOST │───→│ envault │←───│ DB_HOST │ │ DB_PORT │ │ diff │ │ DB_PORT │ │ LOG_LVL │ │ sync │ │ API_KEY │ │ FEATURE_X│ │ rotate │ │ FEATURE_X │ └──────────┘ │ store │ └───────────────┘ └─────────────┘ LOG_LVL=debug │ FEATURE_X=true (stale!) FEATURE_X=true │ MISSING: DB_POOL DB_POOL=10 │ envault sync prod --strategy source_wins

1. Install Envault

$ pip install envault

Or from GitHub:

$ pip install git+https://github.com/Coding-Dev-Tools/envault.git

Verify:

$ envault --help

2. Initialize a Project

Create a configuration file that defines your environments:

$ envault init my-project

This generates a .envault.yml file in the current directory:

project: my-project
environments:
  dev: .env.dev
  staging: .env.staging
  prod: .env.prod
conflict_strategy: source_wins
skip_keys:
  - COMMIT_SHA
  - BUILD_NUMBER

Now create some sample .env files to simulate real environments. Save as .env.dev:

DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp_dev
DB_PASSWORD=dev_password_123
API_KEY=dev_key_abc123
LOG_LEVEL=debug
FEATURE_NEW_CHECKOUT=true
FEATURE_DARK_MODE=false
CACHE_TTL=300

.env.staging:

DB_HOST=staging-db.internal
DB_PORT=5432
DB_NAME=myapp_staging
DB_PASSWORD=staging_password_456
API_KEY=staging_key_def456
LOG_LEVEL=info
CACHE_TTL=600

.env.prod with some drift and a missing key:

DB_HOST=prod-db.internal
DB_PORT=5432
DB_NAME=myapp_prod
DB_PASSWORD=prod_password_789
LOG_LEVEL=warn
CACHE_TTL=1200
🚨 See the drift? API_KEY is missing from prod entirely — that endpoint will 401. FEATURE_NEW_CHECKOUT and FEATURE_DARK_MODE were added to dev but never promoted. CACHE_TTL differs across all three. These are the kinds of silences that cause "works on my machine" bugs in production.

3. Diff Environments

See exactly what's different between any two environments:

$ envault diff dev prod

Envault produces a formatted diff showing keys that are:

═══ Diff: dev → prod ═══

Only in dev:
  FEATURE_NEW_CHECKOUT=true
  FEATURE_DARK_MODE=false
  API_KEY=dev_key_abc123

Only in prod:
  (none)

Different values:
  DB_HOST: localhost → prod-db.internal
  DB_NAME: myapp_dev → myapp_prod
  DB_PASSWORD: dev_password_123 → prod_password_789
  LOG_LEVEL: debug → warn
  CACHE_TTL: 300 → 1200

You can also diff two .env files directly without a project config:

$ envault diff-files .env.dev .env.prod

4. Sync Environments

Promote changes from one environment to another with controlled conflict resolution:

$ envault sync dev prod --dry-run

Always dry-run first. The output shows exactly what would change:

═══ Dry Run: sync dev → prod ═══

Would add:
  FEATURE_NEW_CHECKOUT=true
  FEATURE_DARK_MODE=false
  API_KEY=dev_key_abc123

Would update:
  LOG_LEVEL: warn → debug
  CACHE_TTL: 1200 → 300

When you're ready:

$ envault sync dev prod

Envault also supports different conflict resolution strategies:

# Keep the target value on conflict (don't overwrite prod) $ envault sync dev prod --strategy target_wins # Delete keys in target that don't exist in source $ envault sync dev prod --allow-delete # Skip specific keys during sync $ envault sync dev prod --skip LOG_LEVEL --skip CACHE_TTL
💡 Pro tip: Use --strategy target_wins when syncing to production — you don't want to accidentally overwrite prod credentials with dev values. Use the default source_wins when promoting staging → prod after QA sign-off.

5. Rotate Secrets with Smart Type Inference

Rotating secrets manually is error-prone — someone has to generate a value, update every .env file, and make sure the format is correct. Envault's rotate command handles this automatically:

$ envault rotate DB_PASSWORD

Envault inspects the key name and generates a cryptographically secure value with the correct format:

Key: DB_PASSWORD
Type: database password (inferred from key name)
Generated: L8#mKx2$rT9!qW5@nB4&
Updated in: .env.dev, .env.staging, .env.prod

Smart rotation infers the correct format from the variable name:

Key PatternGenerated FormatExample
DB_PASSWORD, DATABASE_URLNo ambiguous chars, mixed case + symbolsL8#mKx2$rT9!qW5@nB4&
API_KEY, STRIPE_SECRETPrefixed API key with versionsk_live_v2_8aF3...eR7k
JWT_SECRET256-bit base64dGhpcyBpcyBhIDI1Ni1iaXQgc2VjcmV0
WEBHOOK_SECRETLong hex keya1b2c3d4e5f6...7890
Everything else32-char random stringZx9pQ2mN5kL7wR4t...

Additional rotation options:

# Rotate only in production $ envault rotate API_KEY --env prod # Preview without applying $ envault rotate JWT_SECRET --dry-run --show # Generate a longer key $ envault rotate WEBHOOK_SECRET --length 64 # Rotate every secret in an environment $ envault rotate-all --env prod
⚠️ Rotation safety: --dry-run --show lets you preview the new value before it's applied. Always rotate secrets during maintenance windows and verify downstream services after.

6. Secret Store Integrations

Envault can sync with external secret stores — useful when your team uses a centralized vault:

# List secrets in all stores $ envault store list # List secrets with a prefix $ envault store list --prefix /production/ # Get a specific secret $ envault store get DB_PASSWORD --store my-vault # Set a secret $ envault store set DB_PASSWORD new_value --store my-vault

Supported stores: AWS SSM Parameter Store, HashiCorp Vault, Doppler, and 1Password. Configure store connections in your .envault.yml file.

7. CI/CD Integration

Use Envault to verify environment consistency in CI:

# .github/workflows/env-check.yml
name: Environment Variable Check

on:
  pull_request:
    paths:
      - '.env*'
      - '.envault.yml'

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Envault
        run: pip install envault

      - name: Check for drift
        run: |
          echo "=== Comparing staging vs prod ==="
          envault diff staging prod || echo "⚠ Drift detected!"

      - name: Verify no secrets leaked
        run: |
          if grep -r 'password\|secret\|key' .env.* --include='.env*' 2>/dev/null; then
            echo "::warning::Potential secret in .env file"
          fi

For deployments, rotate secrets before release:

# Rotate critical secrets before a production deploy
envault rotate DB_PASSWORD --env prod
envault rotate API_KEY --env prod --dry-run

Command Reference

CommandDescription
envault init <project>Create .envault.yml config file
envault diff <src> <tgt>Show key-level differences between environments
envault diff-files <f1> <f2>Diff two .env files directly
envault sync <src> <tgt>Sync variables with conflict resolution
envault sync --dry-runPreview changes before applying
envault sync --strategy target_winsKeep target values on conflict
envault rotate <key>Rotate a secret with smart type inference
envault rotate --dry-run --showPreview rotated value without applying
envault rotate-all --env prodRotate all secrets in an environment
envault store list/get/setInteract with external secret stores

Real-World Scenario: The Stale Env Rotation

A SaaS team rotated their production database password after a security audit. They updated .env.prod, deployed, and everything worked. Three weeks later, a developer ran the staging environment — and it connected to production. The staging .env file still had the old, un-rotated password that happened to match a legacy read replica's credentials. The incident took two hours to debug.

With Envault, they would have run envault rotate DB_PASSWORD which updates all environments simultaneously. The diff command would have caught the staging-prod mismatch before it caused an incident.


Next Steps

Envault is one of 10 tools in the Revenue Holdings suite. Pair it with other tools that protect your deployments:

  • ConfigDrift — Detect config drift across environments (tutorial)
  • API Contract Guardian — Catch breaking API schema changes in PRs
  • DeployDiff — Preview infra costs and blast radius (tutorial)
  • APIGhost — Mock APIs from OpenAPI specs (tutorial)

Read the full docs →

Get Notified About New Tutorials

DevOps guides, env management tips, and product updates.