envdrift diff¶
Compare two .env files and show differences.
Synopsis¶
Description¶
The diff command compares two .env files and shows:
- Added variables - Present in ENV2 but not ENV1
- Removed variables - Present in ENV1 but not ENV2
- Changed variables - Different values between files
- Unchanged variables - Same in both (with
--include-unchanged)
This is useful for:
- Reviewing differences between development and production
- Auditing environment changes before deployment
- Detecting drift between team members' environments
Arguments¶
| Argument | Description |
|---|---|
ENV1 |
Path to first .env file (baseline) |
ENV2 |
Path to second .env file (comparison) |
Options¶
--schema, -s¶
Schema for sensitive field detection. When provided, sensitive fields are masked in output.
--service-dir, -d¶
Directory to add to Python's sys.path for schema imports.
--format, -f¶
Output format: table (default) or json.
# Human-readable table (default)
envdrift diff .env.dev .env.prod --format table
# Machine-readable JSON
envdrift diff .env.dev .env.prod --format json
--show-values¶
Show actual values instead of masking them. Use with caution - this may expose secrets!
By default, values are shown but sensitive fields (when schema is provided) are masked.
--include-unchanged¶
Include variables that are identical in both files.
--normalize, --strict¶
Normalize values before comparing so trivially-equivalent strings don't show up
as drift. Enabled by default; pass --strict to fall back to raw string compare.
When --normalize is in effect, two values are considered equal if:
- they match after stripping leading/trailing whitespace
(
DATABASE_URL="foo "vsDATABASE_URL=foo), - both look like booleans (
true|false|yes|no|on|off|1|0, any case) and share the same truthiness (DEBUG=truevsDEBUG=True), or - both look like JSON lists/objects and parse to the same structure, regardless
of single- vs double-quote style
(
CORS_ORIGINS=["http://x"]vsCORS_ORIGINS=['http://x']).
When --schema is also passed, each value is additionally coerced through the
matching Pydantic field type (bool, int, list[str], Literal[...], etc.)
before comparison. Variables not in the schema, or values that fail Pydantic
validation, fall back to the universal rules above.
# Default — normalization on
envdrift diff .env.dev .env.prod
# Disable normalization for strict raw-string compare
envdrift diff .env.dev .env.prod --strict
# Schema-aware coercion plus normalization
envdrift diff .env.dev .env.prod --schema config.settings:Settings
Displayed values are always the original ones from the file — normalization
only affects the changed / unchanged classification, never what you see in
the table or JSON output.
Examples¶
Basic Comparison¶
Output:
╭────────────────────── envdrift diff ──────────────────────╮
│ Comparing: .env.development vs .env.production │
╰───────────────────────────────────────────────────────────╯
┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Variable ┃ .env.development┃ .env.production ┃ Status ┃
┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ DEBUG │ true │ false │ changed │
│ LOG_LEVEL │ DEBUG │ WARNING │ changed │
│ SENTRY_DSN │ (missing) │ https://... │ added │
│ DEV_ONLY_VAR │ testing │ (missing) │ removed │
└─────────────────┴─────────────────┴─────────────────┴──────────┘
Summary: 2 changed, 1 added, 1 removed
Drift detected between environments
JSON Output for CI/CD¶
Output:
{
"env1": ".env.development",
"env2": ".env.production",
"summary": {
"added": 1,
"removed": 1,
"changed": 2,
"has_drift": true
},
"differences": [
{
"name": "DEBUG",
"type": "changed",
"value_env1": "true",
"value_env2": "false",
"sensitive": false
},
{
"name": "SENTRY_DSN",
"type": "added",
"value_env1": null,
"value_env2": "https://...",
"sensitive": true
}
]
}
With Schema for Sensitive Detection¶
Sensitive fields (marked with json_schema_extra={"sensitive": True}) are labeled in output.
Show All Variables¶
Expose Values (Use with Caution)¶
CI/CD Drift Detection¶
# GitHub Actions - Comment on PR with drift report
- name: Check drift
id: drift
run: |
envdrift diff .env.development .env.production --format json > drift.json
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const drift = JSON.parse(fs.readFileSync('drift.json', 'utf8'));
if (drift.has_drift) {
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## Environment Drift Detected\n\n` +
`- Added: ${drift.summary.added}\n` +
`- Removed: ${drift.summary.removed}\n` +
`- Changed: ${drift.summary.changed}`
});
}
Diff Types¶
| Type | Description |
|---|---|
added |
Variable exists in ENV2 but not ENV1 |
removed |
Variable exists in ENV1 but not ENV2 |
changed |
Variable exists in both but with different values |
unchanged |
Variable is identical in both files |
Use Cases¶
Pre-Deployment Review¶
Before deploying, compare staging and production:
Onboarding New Team Members¶
Compare your .env with the template:
Detecting Configuration Drift¶
Monitor differences across environments:
for env in staging production; do
echo "=== Development vs $env ==="
envdrift diff .env.development .env.$env
done