init
onboardingInfer a schema from an existing .env or .env.example. New teammates learn what each variable is and where to get it.
wrote dottyenv.toml (6 variables from .env.example) review it, then run `dottyenv check`
.env.example is untyped text. dottyenv replaces it with a schema that says which
variables are required, what they should look like, and where to go get them. A missing key
fails at startup with a link, not twenty minutes in with a stack trace.
brew tap mfung/tap && brew install dottyenv
$ dottyenv check✗ 3 problems in .env INVALID DATABASE_URL expected ^postgres(ql)?://, got "<redacted, 47 chars>" PostgreSQL connection string INVALID LOG_LEVEL expected one of [debug, info, warn, error], got "verbose" MISSING STRIPE_SECRET_KEY Stripe secret key. Use sk_test_ locally. → https://dashboard.stripe.com/apikeys 4 of 6 required variables OK$
// 01 · the schema
Point dottyenv init at the .env.example you already have. It fills in patterns and
source URLs for providers it recognises, and leaves everything else as a TODO.
A wrong pattern is worse than no pattern, so it never guesses.
DATABASE_URL=postgres://localhost/app STRIPE_SECRET_KEY=sk_test_xxx GITHUB_TOKEN= LOG_LEVEL=info PORT=3000
[vars.DATABASE_URL] required = true pattern = '^postgres(ql)?://' description = 'PostgreSQL connection string' secret = true [vars.STRIPE_SECRET_KEY] required = true pattern = '^(sk|rk)_(test|live)_' source = 'https://dashboard.stripe.com/apikeys' secret = true [vars.LOG_LEVEL] required = true # one_of = ["info", ...] # TODO: list the valid values
// 02 · four commands
.env files break.None of them create values. Providers do that. dottyenv handles everything after the value exists.
Infer a schema from an existing .env or .env.example. New teammates learn what each variable is and where to get it.
wrote dottyenv.toml (6 variables from .env.example) review it, then run `dottyenv check`
Validate .env against the schema. Exits non-zero on failure, so it doubles as a CI gate.
MISSING STRIPE_SECRET_KEY → https://dashboard.stripe.com/apikeys $ echo $? 1
Cross-reference the schema against your code. Works in any language, Dockerfiles and CI config included, because it keys off the substring env.
used but not declared SENDGRID_API_KEY src/server.ts:2 SESSION_SECRET app.rb:1 declared but never mentioned LEGACY_UNUSED_FLAG
Every declared variable, whether it's set, and whether it's required. Add --json for scripts.
DATABASE_URL set required GITHUB_TOKEN set required LOG_LEVEL set required STRIPE_SECRET_KEY MISSING required
// 03 · the catalog
Vendors give their secrets recognisable prefixes. dottyenv ships a small catalog of them, so
init writes the pattern and the source URL for you. Patterns match the prefix only,
never the length: a key that changes length next year still passes.
postgres://postgresql://mysql://mysql2://mariadb://mongodb://mongodb+srv://sqlite://sqlite3://file:redis://rediss://AKIAASIA// 04 · environments
One variable, two shapes. Declare it once and override per environment. The overlay is picked
from the filename, so .env.production gets production rules. A misspelled
--env is an error, never a silent downgrade of your production gate.
[vars.DATABASE_URL] required = true description = "Primary database" secret = true [envs.development.vars.DATABASE_URL] pattern = '^(sqlite:|file:)' [envs.production.vars.DATABASE_URL] pattern = '^postgres(ql)?://' [envs.production.vars.STRIPE_SECRET_KEY] required = true pattern = '^sk_live_'
One false ✗ and people stop trusting the output. Patterns are never inferred from a single sample.
Values marked secret are reported by length only. An error message is the last place a password should surface.
No account, no server, no network calls. One TOML file in your repo and one binary on your path.
Data on stdout, diagnostics on stderr, --json everywhere, and exit codes you can branch on in CI.
// 05 · what it is not
dottyenv doesn't store or ship secrets, so it sits next to whatever you already use for that.
| Tool | What it does | How dottyenv differs |
|---|---|---|
| dotenvx | Encrypts .env for git | Doesn't encrypt; it validates |
| direnv | Auto-loads env on cd | Explicit, and runs in CI too |
| SOPS | Encrypts structured files | A different problem entirely |
| Doppler / Infisical | Hosted secret sync | Local-first, no account |
| dotenv-linter | Lints .env syntax | Checks meaning against a schema |
// install
Prebuilt for macOS and Linux on x86_64 and ARM. Linux builds target a glibc 2.28 floor, so they run on RHEL 8, Ubuntu 20.04 and newer.
Every release ships a SHA256SUMS. See Releases for tarballs and the current version.
$ brew tap mfung/tap $ brew install dottyenv
Works on macOS and Linux.
$ curl -LO https://github.com/mfung/dottyenv/releases/latest/download/dottyenv_0.2.1-1_amd64.deb $ sudo dpkg -i dottyenv_0.2.1-1_amd64.deb
On ARM, swap amd64 for arm64.
$ sudo rpm -i https://github.com/mfung/dottyenv/releases/latest/download/dottyenv-0.2.1-1.x86_64.rpm
On ARM, swap x86_64 for aarch64.