envdrift sync¶
Sync encryption keys from cloud vaults to local .env.keys files. This command is specific to dotenvx keys; SOPS users should rely on SOPS/KMS key workflows.
Synopsis¶
Description¶
The sync command fetches DOTENV_PRIVATE_KEY_* secrets from cloud vaults and synchronizes them to local .env.keys files for dotenvx decryption.
This enables secure key distribution without committing keys to source control. Keys are stored in cloud vaults (Azure Key Vault, AWS Secrets Manager, HashiCorp Vault, or GCP Secret Manager) and synced to local development environments or CI/CD pipelines.
If --config is omitted, envdrift auto-discovers envdrift.toml or a pyproject.toml with [tool.envdrift] in the current directory tree.
Supported vault providers:
- Azure Key Vault - Microsoft Azure's secret management service
- AWS Secrets Manager - Amazon Web Services secret storage
- HashiCorp Vault - Open-source secrets management
- GCP Secret Manager - Google Cloud secret storage
Auto-discovery usually supplies provider, vault URL, and region from your config file.
Pass CLI flags when you need to override those defaults or when using legacy pair.txt, and use -c to pin a specific config file (common in CI).
Profile filtering matches pull and lock: without --profile, only untagged mappings are synchronized. With --profile NAME, untagged mappings and
the mappings tagged with that exact profile are synchronized.
Options¶
--config, -c¶
Path to sync configuration file (TOML preferred; legacy pair.txt still supported). Optional when auto-discovery finds envdrift.toml or pyproject.toml
(with [tool.envdrift]).
# Preferred: TOML with provider + mappings (auto-discovered, so -c is optional)
envdrift sync
# Explicit path if needed
envdrift sync --config envdrift.toml
# Legacy: pair.txt (requires provider flags)
envdrift sync --config pair.txt -p azure --vault-url https://myvault.vault.azure.net/
--provider, -p¶
Vault provider to use. Required when the config doesn’t include a provider (e.g., legacy pair.txt); optional otherwise. Use this to override TOML defaults.
Options: azure, aws, hashicorp, gcp
TOML configs usually include the provider; pass --provider to override.
--vault-url¶
Vault URL. Required for Azure and HashiCorp.
Only required when using legacy configs or overriding the TOML defaults. For
HashiCorp, the standard VAULT_ADDR environment variable is also honored as a
fallback. Azure vault URLs must start with https://.
--region¶
AWS region for Secrets Manager. Default: us-east-1.
Only required when using legacy configs or overriding the TOML defaults.
--project-id¶
GCP project ID for Secret Manager. Required for the gcp provider unless configured in TOML.
Only required when using legacy configs or overriding the TOML defaults.
--verify¶
Check only mode. Reports differences without modifying files.
Exits with code 1 when any mapping errors (a mismatch, a missing local key, or a configured secret that is missing from the vault), so CI fails on drift
without needing --ci. A mapping whose folder has no env file yet is only skipped after confirming its configured secret exists in the vault — a
deleted vault secret is an error, not a skip.
--force, -f¶
Force update all mismatches without prompting.
--profile¶
Process regular (untagged) mappings plus mappings tagged with the selected profile. Mappings tagged with other profiles are skipped.
# Sync shared mappings plus the local profile; skip other profile mappings
envdrift sync --profile local
Without --profile, only regular mappings are processed. If the selection is empty, sync exits with code 1 instead of reporting an empty run as a
success.
--check-decryption¶
After syncing, verify that the keys can decrypt .env files.
This tests actual decryption using dotenvx to ensure keys are valid. The check
is non-destructive: the encrypted file and the synced keys file are copied
into a throwaway temp directory and dotenvx decrypts the copy there, so the
working-tree file is never decrypted or rewritten (its bytes are identical
before and after, whether the check passes or fails). Stray
DOTENV_PRIVATE_KEY* variables in your shell are ignored so the verdict
reflects the synced .env.keys, and mappings with relative folder_path
values (the usual monorepo layout) verify correctly from the project root.
If any decryption test fails, sync exits 1 — with or without --ci. The
check also refuses to run when dotenvx is not installed: skipping every test
would verify nothing, so sync reports the missing binary and exits 1
instead of silently succeeding.
--validate-schema¶
Run schema validation after sync.
--schema, -s¶
Schema path for validation (used with --validate-schema).
--service-dir, -d¶
Service directory for schema imports.
--ci¶
CI mode. Exit with code 1 on any errors.
Configuration File Format¶
TOML Format (recommended)¶
In envdrift.toml:
[vault]
provider = "azure" # azure | aws | hashicorp | gcp
[vault.azure]
vault_url = "https://my-keyvault.vault.azure.net/"
[vault.aws]
region = "us-east-1"
[vault.hashicorp]
url = "https://vault.example.com:8200"
[vault.gcp]
project_id = "my-gcp-project"
[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-service-key"
folder_path = "services/auth"
vault_name = "other-vault" # Parsed but informational only — does NOT route (see note below)
environment = "staging" # Use DOTENV_PRIVATE_KEY_STAGING
[[vault.sync.mappings]]
secret_name = "postgres-key"
folder_path = "secrets/postgresql"
environment = "production" # Use DOTENV_PRIVATE_KEY_PRODUCTION
env_file = "postgresql.env" # Custom dotenv filename inside folder_path
[[vault.sync.mappings]]
secret_name = "myapp-local-key"
folder_path = "services/myapp"
profile = "local" # Included only with --profile local
Place the file in the project root so auto-discovery finds it; pass -c envdrift.toml in CI to pin the exact file.
folder_path must be an existing directory
Each mapping's folder_path is validated: a folder that does not exist
(for example a typo like servces/api), or that points at a regular
file instead of a directory, is reported as a per-mapping
error — the row says Mapping folder does not exist or is not a
directory, the summary counts it under Errors, and sync --ci,
pull, and vault-push --all exit non-zero. Only a missing env file
inside an existing folder is a benign skip ("file not created yet").
vault_name / default_vault_name do not switch the vault
vault_name (per-mapping) and default_vault_name are parsed and accepted
but are informational only — the sync/push engine fetches and pushes
every secret from the single vault you configured via --vault-url /
[vault.<provider>] (or --region / --project-id). A per-mapping
vault_name does not route that secret to a different vault, so
vault-push --all pushes every mapping to the one configured vault. To use a
separate vault, run a separate config. See the vault_name row in
configuration.md and the note in
env-file-sync.md.
Legacy Format (pair.txt)¶
# Secret name = folder path
myapp-dotenvx-key=services/myapp
auth-service-key=services/auth
# With a vault-name prefix (parsed but informational only — does NOT route)
myvault/api-service-key=services/api
Format: secret-name=folder-path or vault-name/secret-name=folder-path
The optional vault-name/ prefix is parsed for backward compatibility but, like
the TOML vault_name field, is informational only — the secret is still
fetched from / pushed to the single configured vault (see the note above).
- Lines starting with
#are comments - Empty lines are ignored
- Whitespace is trimmed
pair.txt is still supported, but TOML is recommended for new setups because it captures provider defaults and mappings together.
Examples¶
Azure Key Vault¶
# Basic sync (provider + url in envdrift.toml)
envdrift sync -c envdrift.toml
# Override provider/url on the CLI if needed
envdrift sync -c envdrift.toml -p azure --vault-url https://myvault.vault.azure.net/
# Force update
envdrift sync -c envdrift.toml --force
# Sync regular mappings plus the local profile
envdrift sync -c envdrift.toml --profile local
# Verify mode (CI)
envdrift sync -c envdrift.toml --verify --ci
AWS Secrets Manager¶
# Default region (from TOML)
envdrift sync -c envdrift.toml
# Override region
envdrift sync -c envdrift.toml --region us-west-2
# CI mode with decryption check
envdrift sync -c envdrift.toml --check-decryption --ci
HashiCorp Vault¶
CI/CD Integration¶
These snippets pin -c envdrift.toml so CI runs use the intended config even if the working directory differs.
If your pipeline runs at the repo root and auto-discovery is reliable, you can omit -c.
GitHub Actions¶
jobs:
sync-keys:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Sync encryption keys
run: |
pip install envdrift[azure]
envdrift sync -c envdrift.toml --check-decryption --ci
AWS with OIDC¶
jobs:
sync-keys:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
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: Sync encryption keys
run: |
pip install envdrift[aws]
envdrift sync -c envdrift.toml --check-decryption --ci
Modes¶
Interactive Mode (default)¶
Prompts for confirmation when values mismatch.
Value mismatch for myapp-key:
Local: <redacted len=64 sha=1a2b3c4d>
Vault: <redacted len=64 sha=5e6f7a8b>
Update local file with vault value? (y/N):
Verify Mode (--verify)¶
Reports differences without modifying files. Any error — a local/vault mismatch, a missing local key, or a configured secret that is missing from the
vault — makes the command exit 1, with or without --ci.
x services/myapp (myapp-key, env: production) - error
Error: Local value differs from vault
Local: <redacted len=64 sha=1a2b3c4d>
Vault: <redacted len=64 sha=5e6f7a8b>
Every error row carries its reason. A missing local key is reported with a
message that distinguishes a missing .env.keys file from a file that exists
but lacks the expected key:
x services/myapp (myapp-key, env: production) - error
Error: DOTENV_PRIVATE_KEY_PRODUCTION missing from services/myapp/.env.keys
A mapping whose folder has no env file yet is skipped only after the configured secret is confirmed to exist in the vault; if the secret is missing, verify reports an error instead of a skip:
Force Mode (--force)¶
Updates all mismatches without prompting. Creates backups before updating.
~ services/myapp (myapp-key, env: production) - updated
Backup: services/myapp/.env.keys.backup.20240115_143022_123456
Output¶
Per-Service Status¶
Each row names the mapping's secret and effective environment alongside the
folder, so two mappings sharing a folder_path (for example per-environment
secrets for one service) stay distinguishable:
+ services/myapp (myapp-key, env: production) - created
~ services/auth (auth-key, env: production) - updated
= services/api (api-key, env: production) - skipped
* services/ci (ci-key, env: production) - ephemeral (key not stored locally)
x services/broken (broken-key, env: production) - error
Icons:
+- Created new .env.keys file~- Updated existing file=- Skipped (values match, or env file not created yet)*- Ephemeral (key fetched from vault, deliberately not stored locally)x- Error occurred (vault error, or the mapping'sfolder_pathdoes not exist)
Decryption Test Results¶
Summary Panel¶
╭──────────── Sync Summary ────────────╮
│ Services processed: 3 │
│ Created: 1 │
│ Updated: 1 │
│ Skipped: 1 │
│ Errors: 0 │
│ │
│ Decryption Tests: │
│ Passed: 2 │
│ Failed: 0 │
╰──────────────────────────────────────╯
All services synced successfully
When ephemeral mode is in use, the summary adds an Ephemeral: line counting
the services whose keys were fetched but deliberately not stored locally:
Exit Codes¶
| Code | Meaning |
|---|---|
| 0 | Success (all synced, no errors) |
| 1 | Error (vault error, sync failure, decryption failure) |
| 1 | --ci: any per-mapping error, including a folder_path that does not exist |
| 1 | --check-decryption: any decryption test failed (even without --ci) |
| 1 | --check-decryption: dotenvx is not installed (nothing can be verified) |
Authentication¶
Azure Key Vault¶
Uses Azure Identity's DefaultAzureCredential, which tries in order:
- Environment variables (
AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_CLIENT_SECRET) - Managed Identity (in Azure)
- Azure CLI (
az login) - VS Code Azure extension
- Interactive browser
AWS Secrets Manager¶
Uses boto3's credential chain:
- Environment variables (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY) - Shared credential file (
~/.aws/credentials) - IAM role (EC2, ECS, Lambda)
HashiCorp Vault¶
For sync, the only supported authentication method is the VAULT_TOKEN environment variable.
(The vault client also accepts a token parameter, but that is internal/programmatic only and is not exposed as a CLI flag.)
Security Notes¶
.env.keysfiles are created with600permissions (owner read/write only)- Backups are created before updates
- Never commit
.env.keysto version control - Add
.env.keysto your.gitignore
See Also¶
- encrypt - Check/perform encryption
- decrypt - Decrypt .env files
- Env File Sync Guide - Detailed setup guide