Vault Sync Guide¶
This guide covers setting up vault synchronization to distribute encryption keys securely across your team and CI/CD pipelines. Vault sync is specific to dotenvx; SOPS users should manage keys with their SOPS/KMS setup.
Overview¶
When using dotenvx for encryption, each environment has a private key stored in .env.keys. These keys:
- Should never be committed to version control
- Need to be shared securely with team members
- Must be available in CI/CD for decryption
envdrift provides two commands for vault synchronization:
envdrift pull- Pull keys from vault AND decrypt all env files (recommended for onboarding)envdrift sync- Syncs keys only (useful when you need more control)
Verify vault key can decrypt (drift detection)¶
To ensure the encrypted file matches the shared vault key (and to catch key drift), run:
envdrift decrypt .env.production --verify-vault --ci \
-p azure --vault-url https://myvault.vault.azure.net \
--secret myapp-dotenvx-key
If the vault key cannot decrypt the file, it exits 1 and prints repair steps:
git restore .env.productionenvdrift sync --force(auto-discovers envdrift.toml; add-c envdrift.tomlor provider flags only if you need to override)envdrift encrypt .env.production
Team workflow (day to day)¶
- One-time setup
- Store the private key in vault (Azure/AWS/HashiCorp/GCP).
- Add
envdrift pullto onboarding docs - one command gets developers ready. -
Add a pre-commit or CI job that runs:
envdrift decrypt .env.production --verify-vault --ci \ -p azure --vault-url https://myvault.vault.azure.net \ --secret myapp-dotenvx-keyThis fails fast on key drift.
-
Developer onboarding (one command)
Or if you need more control:
This writes .env.keys for dotenvx (never commit this file).
- Encrypt changes
- If drift is detected
git restore .env.productionenvdrift sync --force(-c envdrift.toml if auto-discovery doesn't find the file)envdrift encrypt .env.production
Architecture¶
┌─────────────────┐
│ Cloud Vault │
│ (Azure/AWS/HC) │
│ GCP │
│ │
│ ┌───────────┐ │
│ │ app-key │ │
│ │ auth-key │ │
│ │ api-key │ │
│ └───────────┘ │
└────────┬────────┘
│
│ envdrift sync
│
▼
┌─────────────────────────────────────┐
│ Local Environment │
│ │
│ services/ │
│ app/.env.keys ◄── app-key │
│ auth/.env.keys ◄── auth-key │
│ api/.env.keys ◄── api-key │
│ │
└─────────────────────────────────────┘
Quick Start¶
1. Install with vault support¶
# Azure Key Vault
pip install envdrift[azure]
# AWS Secrets Manager
pip install envdrift[aws]
# HashiCorp Vault
pip install envdrift[hashicorp]
# GCP Secret Manager
pip install envdrift[gcp]
# All providers
pip install envdrift[vault]
2. Create a configuration file¶
Preferred: envdrift.toml in your project root:
[vault]
provider = "azure" # or aws/hashicorp/gcp
[vault.azure]
vault_url = "https://my-keyvault.vault.azure.net/"
[vault.gcp]
project_id = "my-gcp-project"
[vault.sync]
default_vault_name = "my-keyvault"
max_workers = 4 # Optional: parallelize pull/lock env file operations
[[vault.sync.mappings]]
secret_name = "myapp-dotenvx-key"
folder_path = "services/myapp"
[[vault.sync.mappings]]
secret_name = "auth-service-dotenvx-key"
folder_path = "services/auth"
environment = "staging"
Legacy (still supported): pair.txt with secret-name=folder-path lines. Use only when you can’t add TOML.
3. Store your keys in the vault¶
Azure Key Vault:
az keyvault secret set \
--vault-name my-keyvault \
--name myapp-dotenvx-key \
--value "$(cat services/myapp/.env.keys | grep DOTENV_PRIVATE_KEY_PRODUCTION | cut -d'=' -f2)"
AWS Secrets Manager:
aws secretsmanager create-secret \
--name myapp-dotenvx-key \
--secret-string "$(cat services/myapp/.env.keys | grep DOTENV_PRIVATE_KEY_PRODUCTION | cut -d'=' -f2)"
HashiCorp Vault:
vault kv put secret/myapp-dotenvx-key \
value="$(cat services/myapp/.env.keys | grep DOTENV_PRIVATE_KEY_PRODUCTION | cut -d'=' -f2)"
GCP Secret Manager:
gcloud secrets create myapp-dotenvx-key --replication-policy="automatic"
printf "%s" "$(cat services/myapp/.env.keys | grep DOTENV_PRIVATE_KEY_PRODUCTION | cut -d'=' -f2)" \
| gcloud secrets versions add myapp-dotenvx-key --data-file=-
4. Sync keys locally¶
Auto-discovery finds envdrift.toml (or [tool.envdrift] in pyproject.toml) in your project tree.
Add -c envdrift.toml when running outside the repo root or if you want CI to pin the exact file.
# Auto-discovery (Azure/AWS/HashiCorp/GCP)
envdrift sync
# Explicit path when auto-discovery isn’t available
envdrift sync -c envdrift.toml
Provider Setup¶
Azure Key Vault¶
Prerequisites¶
- Azure CLI installed and logged in
- Key Vault created
- Access policy configured
Authentication Methods¶
Environment Variables:
Azure CLI:
Managed Identity (in Azure): No configuration needed - automatically uses the VM/App Service identity.
Access Policy¶
Grant your identity these permissions:
- Get - Read secrets
- List - List secret names
az keyvault set-policy \
--name my-keyvault \
--upn user@example.com \
--secret-permissions get list
Example¶
AWS Secrets Manager¶
Prerequisites¶
- AWS CLI configured
- IAM permissions for Secrets Manager
Authentication Methods¶
Environment Variables:
Credentials File:
IAM Role (EC2/ECS/Lambda): No configuration needed - automatically uses instance profile.
IAM Policy¶
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:ListSecrets"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789:secret:*-dotenvx-key*"
}
]
}
Example¶
# Configure credentials
aws configure
# Sync (region/provider can live in envdrift.toml; auto-discovered)
envdrift sync
HashiCorp Vault¶
Prerequisites¶
- Vault server running
- Token with read access
- KV v2 secrets engine enabled
Authentication¶
Environment Variable:
Token File:
Policy¶
Example¶
GCP Secret Manager¶
Prerequisites¶
- gcloud CLI installed and authenticated
- Secret Manager API enabled for the project
- Credentials available via Application Default Credentials
Authentication¶
Application Default Credentials:
Service Account Key (CI):
Example¶
Configuration¶
TOML Format (envdrift.toml) — recommended¶
[vault]
provider = "azure"
[vault.azure]
vault_url = "https://my-keyvault.vault.azure.net/"
[vault.sync]
default_vault_name = "my-keyvault"
env_keys_filename = ".env.keys"
[[vault.sync.mappings]]
secret_name = "myapp-key"
folder_path = "services/myapp"
[[vault.sync.mappings]]
secret_name = "auth-key"
folder_path = "services/auth"
environment = "staging" # Use DOTENV_PRIVATE_KEY_STAGING
[[vault.sync.mappings]]
secret_name = "prod-key"
folder_path = "services/prod"
vault_name = "production-vault" # Override default
Ephemeral Keys Mode¶
Ephemeral keys mode prevents local storage of .env.keys files.
Keys are fetched from vault on-demand and passed directly to dotenvx via environment variables.
This is ideal for:
- CI/CD pipelines where you don't want keys persisted to disk
- Security-sensitive environments where keys should only exist in memory
- Ephemeral workloads that spin up and down frequently
Central Configuration¶
Enable for all mappings:
[vault.sync]
ephemeral_keys = true # Never store .env.keys locally
[[vault.sync.mappings]]
secret_name = "myapp-key"
folder_path = "."
Per-Mapping Configuration¶
Enable for specific services:
[vault.sync]
# Central: default behavior (store keys locally)
[[vault.sync.mappings]]
secret_name = "dev-key"
folder_path = "services/dev"
# Uses local .env.keys (default)
[[vault.sync.mappings]]
secret_name = "ci-key"
folder_path = "services/ci"
ephemeral_keys = true # Override: never store locally
How It Works¶
envdrift pullfetches keys from vault- Keys are passed to dotenvx via
DOTENV_PRIVATE_KEY_*env vars - Files are decrypted in-place
- No
.env.keysfile is created
Note: In ephemeral mode, if the vault is unavailable, commands will fail. There is no fallback to local keys.
Profile Configuration¶
Profiles allow multiple environment configurations in a single project (e.g., local development, staging, production).
[vault]
provider = "azure"
[vault.azure]
vault_url = "https://my-keyvault.vault.azure.net/"
[vault.sync]
default_vault_name = "my-keyvault"
# Regular mapping (always processed by pull/sync)
[[vault.sync.mappings]]
secret_name = "shared-key"
folder_path = "."
# Profile: local development
[[vault.sync.mappings]]
secret_name = "local-key"
folder_path = "."
profile = "local" # Only with --profile local
activate_to = ".env" # Copy decrypted .env.local to .env
# Profile: production
[[vault.sync.mappings]]
secret_name = "prod-key"
folder_path = "."
profile = "prod"
activate_to = ".env"
Usage:
# Pull local development environment
envdrift pull --profile local
# Pull production environment
envdrift pull --profile prod
Key concepts:
profile: Tags a mapping for filtering. Without--profile, only non-profile mappings are processed.activate_to: Path to copy the decrypted file to (e.g.,.env). Useful for apps that expect a plain.envfile.environment: If omitted, defaults to theprofilevalue (e.g., profilelocal→ looks for.env.local).
Legacy Format (pair.txt)¶
# Comments start with #
secret-name=folder-path
# Multiple services
myapp-dotenvx-key=services/myapp
auth-service-key=services/auth-service
api-gateway-key=services/api
# With explicit vault name (Azure)
production-vault/prod-key=services/prod
pair.txt remains supported for backwards compatibility, but TOML is preferred because it stores provider defaults and mappings together.
CI/CD Integration¶
GitHub Actions - Azure¶
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Install envdrift
run: pip install envdrift[azure]
- name: Sync encryption keys
run: |
envdrift sync --force --ci
- name: Decrypt environment files
run: envdrift decrypt .env.production
- name: Deploy
run: ./deploy.sh
GitHub Actions - AWS with OIDC¶
name: Deploy
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/github-actions
aws-region: us-east-1
- name: Install envdrift
run: pip install envdrift[aws]
- name: Sync and verify keys
run: |
envdrift sync --check-decryption --ci
GitLab CI¶
deploy:
image: python:3.11
script:
- pip install envdrift[azure]
- envdrift sync --force --ci
- envdrift decrypt .env.production
- ./deploy.sh
variables:
AZURE_CLIENT_ID: $AZURE_CLIENT_ID
AZURE_TENANT_ID: $AZURE_TENANT_ID
AZURE_CLIENT_SECRET: $AZURE_CLIENT_SECRET
Workflows¶
Initial Setup¶
- Generate encryption keys locally:
- Store key in vault:
az keyvault secret set \
--vault-name my-keyvault \
--name myapp-dotenvx-key \
--value "$(grep DOTENV_PRIVATE_KEY_PRODUCTION .env.keys | cut -d'=' -f2)"
- Create envdrift.toml:
[vault]
provider = "azure"
[vault.azure]
vault_url = "https://my-keyvault.vault.azure.net/"
[vault.sync]
default_vault_name = "my-keyvault"
[[vault.sync.mappings]]
secret_name = "myapp-dotenvx-key"
folder_path = "."
- Add .env.keys to .gitignore:
- Commit encrypted .env files and config:
New Team Member Onboarding¶
- Clone the repository
- Get vault access from team lead
- Run pull (syncs keys AND decrypts all env files in one command):
That's it! The pull command handles everything automatically.
Alternative (manual approach):
If you need more control, you can run the steps separately:
Key Rotation¶
- Generate new key:
- Update vault:
az keyvault secret set \
--vault-name my-keyvault \
--name myapp-dotenvx-key \
--value "$(grep DOTENV_PRIVATE_KEY_PRODUCTION .env.keys | cut -d'=' -f2)"
- Team syncs:
Troubleshooting¶
Authentication Errors¶
Azure:
AWS:
HashiCorp:
Secret Not Found¶
Check secret name matches exactly:
# Azure
az keyvault secret list --vault-name my-keyvault
# AWS
aws secretsmanager list-secrets
# HashiCorp
vault kv list secret/
Permission Denied¶
Verify your identity has required permissions:
- Azure: Get, List on Key Vault secrets
- AWS: secretsmanager:GetSecretValue, secretsmanager:ListSecrets
- HashiCorp: read, list on secret path
Verify Mode Fails¶
Use --verify to check without modifying:
This shows what would change without making modifications.
Best Practices¶
- Use separate vaults per environment - Production keys in production vault
- Rotate keys regularly - Especially after team changes
- Limit vault access - Only CI/CD and necessary team members
- Use OIDC in CI/CD - Avoid storing long-lived credentials
- Verify in CI before deploy - Use
--check-decryption --ci - Backup keys - Store copies in secure location
- Use ephemeral keys in CI/CD - Set
ephemeral_keys = trueto avoid persisting keys to disk
See Also¶
- pull command - One-command setup (recommended)
- sync command - Sync keys only
- encrypt - Encryption command
- decrypt - Decryption command