envdrift lock¶
Verify keys and encrypt all env files (opposite of pull - prepares for commit).
Synopsis¶
Description¶
The lock command is the opposite of pull. While pull syncs keys and decrypts files for
development, lock verifies keys and encrypts files before committing.
The key-sync/verify part of this workflow is dotenvx-only. The encrypt step
itself uses your configured backend, so lock can drive SOPS encryption when a
[vault.sync] section is present — but the recommended SOPS path is plain
envdrift encrypt. See the SOPS Backend Guide.
This command ensures your environment files are properly encrypted before committing. It can:
- Verify key consistency - Check that local
.env.keysmatch vault secrets (prevents key drift) - Sync keys from vault - Optionally fetch keys from vault to ensure consistency
- Encrypt env files - Encrypt all decrypted
.env.<environment>files using the configured backend (dotenvx or SOPS)
This is the recommended command before committing changes to ensure:
- Local encryption keys are in sync with the team's vault keys
- All .env files are properly encrypted
- No plaintext secrets are accidentally committed
Configuration is auto-discovered from:
pyproject.tomlwith[tool.envdrift.vault.sync]sectionenvdrift.tomlwith[vault.sync]section- Explicit
--configfile
Options¶
--config, -c¶
Path to sync configuration file (TOML preferred; legacy pair.txt still supported).
--provider, -p¶
Vault provider to use. Options: azure, aws, hashicorp, gcp.
Usually read from TOML config; use this to override.
--vault-url¶
Vault URL. Required for Azure and HashiCorp providers.
Usually read from TOML config; use this to override.
--region¶
AWS region for Secrets Manager. Default: us-east-1.
--project-id¶
GCP project ID for Secret Manager. Required for the gcp provider unless configured in TOML.
--force, -f¶
Force encryption without prompting.
--profile¶
Process all regular (non-profile) mappings plus the mapping tagged with this profile.
--verify-vault¶
Verify that local .env.keys match vault secrets before encrypting.
This helps detect key drift where a developer may have re-encrypted with a new key that doesn't match the team's shared vault key.
Verification is strict: a mapping that cannot be verified — missing
.env.keys, a missing key entry, a missing or empty vault secret, or a vault
access error — fails the same way a key mismatch does. When any mapping fails
verification, lock exits 1 before encrypting anything (even with
--force, which only skips prompts). This prevents the encryption step from
silently minting a fresh local-only key that the rest of the team cannot
decrypt with the vault key. Repair with envdrift lock --sync-keys (fetch
keys from vault) or envdrift vault-push (publish local keys), or rerun
without --verify-vault to encrypt without verification.
--sync-keys¶
Sync keys from vault before encrypting. This implies --verify-vault.
Use this to ensure your local keys are up-to-date with vault before encrypting.
The same hard-failure contract applies: when any mapping's key fails to sync
(missing vault secret, vault error), lock exits 1 before encrypting
anything — even with --force. Encrypting past a failed sync would mint a
fresh local-only key for the very mapping whose vault key is unavailable.
The verification phase checks that every configured vault secret exists and
contains usable key material even when its mapping has no local env file yet;
an unavailable or unusable secret is an error, not a successful skip.
--check¶
Check encryption status only (dry run). Reports what would be encrypted without making changes.
A file counts as encrypted only when no plaintext value remains — the same predicate
envdrift push uses, applied to every backend (dotenvx and SOPS alike). A mixed file
holding even one freshly added plaintext secret fails the check, no matter how many other
values are already ciphertext. The plaintext DOTENV_PUBLIC_KEY_* header line and SOPS's
sops_* metadata trailer are ignored, so fully-encrypted files pass regardless of how few
variables they hold.
lock --check is deliberately stricter than envdrift encrypt --check: the latter
flags only values that look like secrets (sensitive names, suspicious values, or
schema-marked fields), while lock --check fails on any plaintext value left in a file
that is supposed to be fully encrypted.
Exits 1 when any file still needs encryption (including, with --all,
partial-encryption .secret files); exits 0 only when everything is encrypted.
--all¶
Include partial encryption files in the locking process. When set:
- No longer skips combined files - Partial encryption combined files (e.g.,
.env.production) get encrypted like regular files - Encrypts
.secretfiles - Ensures partial encryption secret files are also locked - Deletes combined files - Removes the merged combined files after locking
This is useful when you want to lock everything and clean up generated files before committing.
Secrets-only environments are skipped — but still checked. Combine-mode handling does not apply to environments configured with
secrets_only = true— they have no combined file and are encrypted in place withenvdrift push.lock --allreports them as skipped and leaves them untouched. If a skipped environment still holds plaintext secrets, the final summary says so andlock --allexits 1 instead of printing the green "ready to commit" banner — runenvdrift pushto encrypt them.
# Lock everything including partial encryption files
envdrift lock --all
# Force mode with partial encryption
envdrift lock -f --all
# Check what would happen
envdrift lock --check --all
Smart Encryption¶
When smart_encryption is enabled in your configuration, the lock command will skip
re-encryption if the file content hasn't changed. This reduces unnecessary git noise
caused by non-deterministic encryption algorithms used by dotenvx and SOPS.
The Problem¶
Both dotenvx (ECIES) and SOPS use encryption that produces different ciphertext each time, even for identical plaintext. This means:
- Decrypting and re-encrypting an unchanged file creates a "modified" file in git
- Git history becomes cluttered with meaningless changes
- Code reviews show confusing diffs
The Solution¶
With smart encryption enabled:
- Before re-encrypting, the command compares current content with the decrypted git version
- If content is unchanged, it restores the original encrypted file from git
- No new ciphertext is generated, so git shows no changes
Configuration¶
Enable in your envdrift.toml:
Example Output¶
Step 1: Encrypting environment files...
= .env.production - skipped (content unchanged, restored encrypted version from git)
+ .env.staging - encrypted
Examples¶
Basic Lock¶
Verify Keys Then Lock¶
Sync Keys Then Lock¶
Check-Only Mode (Dry Run)¶
Lock With Profile¶
Lock Including Partial Encryption Files¶
Force Lock Without Prompts¶
Override Provider Settings¶
Output¶
Without Vault Verification¶
Lock - Verifying keys and encrypting env files
Provider: azure | Mode: Interactive | Services: 3
Step 1: Encrypting environment files...
+ services/myapp/.env.production - encrypted
= services/auth/.env.production - skipped (already encrypted)
╭──────────── Lock Summary ────────────╮
│ Encrypted: 1 │
│ Already encrypted: 1 │
│ Skipped: 0 │
│ Errors: 0 │
╰──────────────────────────────────────╯
Lock complete! Your environment files are encrypted and ready to commit.
With Vault Verification¶
Lock - Verifying keys and encrypting env files
Provider: azure | Mode: Interactive | Services: 2
Step 1: Verifying keys with vault...
✓ services/myapp - keys match vault
✗ services/auth - KEY MISMATCH: local key differs from vault!
[ERROR] Found 1 failed key verification(s). Nothing was encrypted.
Run 'envdrift lock --sync-keys' to sync keys from vault, or rerun without
--verify-vault to skip verification.
An unverifiable mapping fails the same way (exit 1, nothing encrypted):
Step 1: Verifying keys with vault...
✗ services/auth - cannot verify: .env.keys not found
[ERROR] Found 1 failed key verification(s). Nothing was encrypted.
Run 'envdrift lock --sync-keys' to sync keys from vault, or rerun without
--verify-vault to skip verification.
A vault secret that cannot be parsed as key material at all (a JSON document without a usable
key field, a multi-line blob with no key line, a binary payload) is reported as
KEY UNUSABLE with the shape problem, and the summary counts it as an unusable vault key
rather than a failed key verification — fix the secret in the vault; --sync-keys cannot install it.
With Key Sync¶
Lock - Verifying keys and encrypting env files (profile: local)
Provider: azure | Mode: FORCE | Services: 1
Step 1: Verifying keys with vault...
Processing: . (local-key, env: local)
+ . (local-key, env: local) - created
╭──────────── Sync Summary ────────────╮
│ Services processed: 1 │
│ Created: 1 │
│ Updated: 0 │
│ Skipped: 0 │
│ Errors: 0 │
╰──────────────────────────────────────╯
Step 2: Encrypting environment files...
+ ./.env.local - encrypted
╭──────────── Lock Summary ────────────╮
│ Encrypted: 1 │
│ Already encrypted: 0 │
│ Skipped: 0 │
│ Errors: 0 │
╰──────────────────────────────────────╯
Lock complete! Your environment files are encrypted and ready to commit.
With --all (Partial Encryption)¶
Lock - Verifying keys and encrypting env files
Provider: azure | Mode: FORCE | Services: 3 | Including partial encryption
Step 1: Encrypting environment files...
+ services/api/.env.production - encrypted
= services/auth/.env.production - skipped (already encrypted)
+ synapse/.env.production - encrypted
Step 2: Processing partial encryption files...
= synapse/.env.production.secret - skipped (already encrypted)
- synapse/.env.production - deleted (combined file)
╭──────────── Lock Summary ────────────╮
│ Encrypted: 2 │
│ Already encrypted: 2 │
│ Skipped: 0 │
│ Errors: 0 │
│ Partial secrets encrypted: 0 │
│ Combined files deleted: 1 │
╰──────────────────────────────────────╯
Lock complete! Your environment files are encrypted and ready to commit.
Warnings and Errors¶
The lock command catches many edge cases and provides helpful warnings and errors:
Warnings¶
| Warning | Meaning |
|---|---|
.env.keys not found |
No encryption keys file exists; a new key will be generated |
{KEY} missing from .env.keys |
The expected private key for this environment isn't in the keys file |
vault secret is empty |
The vault secret exists but has no value |
vault secret not found |
The secret doesn't exist in the vault |
multiple .env files found |
Multiple .env.* files exist; specify the environment explicitly |
file not found |
The expected .env.<environment> file doesn't exist |
partially encrypted (plaintext values remain) |
The file mixes ciphertext with plaintext values; will re-encrypt |
partial encryption combined file, use --all to include |
File is a generated combined file; use --all to include |
Errors¶
| Error | Meaning |
|---|---|
KEY MISMATCH |
Local key differs from vault key - potential key drift |
KEY UNUSABLE |
The vault secret's key material is malformed or unusable - fix the vault secret shape |
vault error |
Failed to access or authenticate with the vault |
encryption failed |
The configured backend (dotenvx or SOPS) failed to encrypt the file |
Key sync failed |
Could not sync keys from vault |
Exit Codes¶
| Code | Meaning |
|---|---|
| 0 | Success (all files encrypted or verified) |
| 1 | Error (key mismatch, encryption failure, or vault error) |
| 1 | A mapping's folder_path does not exist (broken sync config, reported per row) |
| 1 | A mapped env file or .env.keys cannot be read (e.g. not valid UTF-8) — per-file error |
| 1 | --verify-vault: a mapping failed or could not be verified (nothing is encrypted) |
| 1 | --check: one or more files still need encryption |
| 1 | --all: a skipped secrets-only environment still holds plaintext (run envdrift push) |
Configuration File Format¶
Same as envdrift pull and envdrift sync. See sync documentation for details.
Example envdrift.toml:
[vault]
provider = "azure"
[vault.azure]
vault_url = "https://my-keyvault.vault.azure.net/"
[vault.sync]
default_vault_name = "my-keyvault"
# Regular mapping (always processed)
[[vault.sync.mappings]]
secret_name = "myapp-key"
folder_path = "services/myapp"
environment = "production"
[[vault.sync.mappings]]
secret_name = "postgres-key"
folder_path = "secrets/postgresql"
environment = "production" # Key stays DOTENV_PRIVATE_KEY_PRODUCTION
env_file = "postgresql.env" # Encrypt secrets/postgresql/postgresql.env
# Profile mappings (processed only with --profile)
[[vault.sync.mappings]]
secret_name = "local-key"
folder_path = "."
profile = "local"
activate_to = ".env"
Workflow: pull and lock¶
The pull and lock commands form a complete development workflow:
# Starting development (decrypt files)
envdrift pull
# ... make changes to .env files ...
# Before committing (encrypt files)
envdrift lock --verify-vault
# If keys are out of sync
envdrift lock --sync-keys
CI/CD Pre-commit Hook¶
# pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: envdrift-lock
name: Ensure env files are encrypted
entry: envdrift lock --check --force
language: system
files: ^\.env\.(production|staging|development)$
pass_filenames: false
Prerequisites¶
- Cloud vault credentials configured (Azure CLI, AWS credentials, etc.)
- The configured encryption backend installed (
dotenvxor SOPS)
See Also¶
- pull - Pull keys and decrypt files (opposite of lock)
- sync - Sync keys only (without encryption/decryption)
- encrypt - Encrypt a single .env file
- decrypt - Decrypt a single .env file
- vault-push - Push keys to vault
- vault-pull - Config-free single-secret pull + decrypt