Flatten any format
.env, YAML, JSON and Kubernetes ConfigMaps. The format comes from the file name; nested keys become paths.
# app.yaml
db:
host: db.internal
pool:
max: 20
flattens to
db.host "db.internal" db.pool.max 20
Somewhere between the two, a config key went missing or a timeout lost a zero. confdrift compares your environment configs and tells you what drifted, before the deploy does.
go install github.com/eduardofrafre/confdrift@latest$ confdrift staging/configmap.yaml production/configmap.yaml DATABASE_URL changed staging "postgres://checkout:<redacted>@db.staging.example.com:5432/checkout" production "postgres://checkout:<redacted>@db.prod.example.com:5432/checkout" FEATURE_NEW_CART missing from production staging "true" production (not set) LOG_LEVEL changed staging "debug" production "info" PAYMENTS_TIMEOUT_MS changed staging "3000" production "300" STRIPE_API_KEY changed staging <redacted> production <redacted> 5 of 6 keys drifted across 2 files (1 missing, 4 changed). $ echo $? 1
The problem
Reordered keys, different indentation or a YAML file next to a JSON one, and diff shows noise everywhere and the real change nowhere.
A feature flag that only exists in staging. A timeout of 300 where it should be 3000. A number that turned into a string. Nobody sees it in review.
Drift is invisible until the code that depends on it reaches production. confdrift moves that moment to a pull request.
How it works
confdrift never compares text. It turns each file into a flat list of key paths and typed values, so formats, ordering and indentation stop mattering, and only real differences are left.
.env, YAML, JSON and Kubernetes ConfigMaps. The format comes from the file name; nested keys become paths.
# app.yaml
db:
host: db.internal
pool:
max: 20
flattens to
db.host "db.internal" db.pool.max 20
Each key is checked across all the files at once, two environments or ten. A key is the same, changed, or missing somewhere.
| key | stg | prod | |
|---|---|---|---|
| RETRY_LIMIT | "5" | "5" | same |
| TIMEOUT_MS | "3000" | "300" | changed |
| NEW_CART | "true" | - | missing |
A readable report for you, JSON for scripts, credentials redacted, and an exit code a pipeline can act on.
Try it
This is the real confdrift engine compiled to WebAssembly, running in your browser. Nothing you type leaves this page. Rename a file to change its format.
Same rules as the CLI: types count, so 5432 and "5432" differ, and keys that look like credentials are compared but printed as <redacted>.
Using it
# two environments confdrift .env.staging .env.production # or any number of them confdrift dev.yaml staging.yaml prod.yaml # only keys missing somewhere confdrift a.env b.env --keys-only # skip keys expected to differ confdrift a.env b.env --ignore 'DATABASE_*' # for scripts and bots confdrift a.env b.env --format json
Flags can go before or after the file names. --ignore takes * wildcards and can be repeated.
# .github/workflows/config-drift.yml
on: pull_request
jobs:
drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v6
- run: go run github.com/eduardofrafre/confdrift@latest --keys-only deploy/staging.env deploy/production.env
--keys-only is usually what a pipeline wants: values are expected to differ between environments, but a key defined in one and not the other is a bug waiting for a deploy.
.env, .env.production, prod.env. Quotes, export, comments and multi-line values are understood. ${VARS} are not expanded: if the reference changed, that is drift.db.pool.max or servers[0], so a YAML file can be compared with a JSON one.data keys. Output of helm template or kustomize build works as is: other resources are skipped.Values of keys that look like credentials (*PASSWORD*, *TOKEN*, *SECRET*, *API_KEY* and similar) print as <redacted>, and so do passwords inside URLs. They are still compared, so a rotated secret still shows up as drift. --show-secrets prints them.
| exit | meaning |
|---|---|
| 0 | The files agree |
| 1 | Drift found |
| 2 | Bad input: unreadable file, parse error, unknown flag |
Install
About 3 MB, and it compares a real config in a couple of milliseconds. Pick your platform:
x86-64 shown; for ARM replace amd64 with arm64.
curl -sL https://github.com/eduardofrafre/confdrift/releases/download/v0.1.0/confdrift_0.1.0_linux_amd64.tar.gz | tar xz sudo mv confdrift_0.1.0_linux_amd64/confdrift /usr/local/bin/ confdrift --version
Apple silicon shown; on an Intel Mac replace arm64 with amd64.
curl -sL https://github.com/eduardofrafre/confdrift/releases/download/v0.1.0/confdrift_0.1.0_darwin_arm64.tar.gz | tar xz sudo mv confdrift_0.1.0_darwin_arm64/confdrift /usr/local/bin/ confdrift --version
Download confdrift_0.1.0_windows_amd64.zip (or the arm64 build), unzip it, and put confdrift.exe on your PATH. Then, in PowerShell:
confdrift .env.staging .env.production
With Go 1.24 or newer:
go install github.com/eduardofrafre/confdrift@latest
Every build, with checksums, is on the releases page.
Open source
Found drift confdrift missed, or a format it should read? Open an issue. If it caught something before a deploy did, you can support it by PayPal.