a better dotenv–from the creator of dotenv
.
- run anywhere (cross-platform)
- multi-environment
- encrypted envs
Install and use it in code just like dotenv
.
npm install @dotenvx/dotenvx --save
// index.js
require('@dotenvx/dotenvx').config()
console.log(`Hello ${process.env.HELLO}`)
Or install globally
brew install dotenvx/brew/dotenvx
Intall globally as a cli to unlock dotenv for ANY language, framework, or platform. 💥
I am using (and recommending) this approach going forward. – motdotla
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ node index.js
Hello undefined
$ dotenvx run -- node index.js
Hello World
> :-D
More examples
-
TypeScript 📘
// package.json { "type": "module", "dependencies": { "chalk": "^5.3.0" } }
// index.ts import chalk from 'chalk' console.log(chalk.blue(`Hello ${process.env.HELLO}`))
$ npm install $ echo "HELLO=World" > .env $ dotenvx run -- npx tsx index.ts Hello World
-
Deno 🦕
$ echo "HELLO=World" > .env $ echo "console.log('Hello ' + Deno.env.get('HELLO'))" > index.ts $ deno run --allow-env index.ts Hello undefined $ dotenvx run -- deno run --allow-env index.ts Hello World
-
Bun 🥟
$ echo "HELLO=Test" > .env.test $ echo "console.log('Hello ' + process.env.HELLO)" > index.js $ bun index.js Hello undefined $ dotenvx run -f .env.test -- bun index.js Hello Test
-
Python 🐍
$ echo "HELLO=World" > .env $ echo 'import os;print("Hello " + os.getenv("HELLO", ""))' > index.py $ dotenvx run -- python3 index.py Hello World
-
PHP 🐘
$ echo "HELLO=World" > .env $ echo '<?php echo "Hello {$_SERVER["HELLO"]}\n";' > index.php $ dotenvx run -- php index.php Hello World
-
Ruby 💎
$ echo "HELLO=World" > .env $ echo 'puts "Hello #{ENV["HELLO"]}"' > index.rb $ dotenvx run -- ruby index.rb Hello World
-
Go 🐹
$ echo "HELLO=World" > .env $ echo 'package main; import ("fmt"; "os"); func main() { fmt.Printf("Hello %s\n", os.Getenv("HELLO")) }' > main.go $ dotenvx run -- go run main.go Hello World
-
Rust 🦀
$ echo "HELLO=World" > .env $ echo 'fn main() {let hello = std::env::var("HELLO").unwrap_or("".to_string());println!("Hello {hello}");}' > src/main.rs $ dotenvx run -- cargo run Hello World
-
Java ☕️
$ echo "HELLO=World" > .env $ echo 'public class Index { public static void main(String[] args) { System.out.println("Hello " + System.getenv("HELLO")); } }' > index.java $ dotenvx run -- java index.java Hello World
-
.NET 🔵
$ dotnet new console -n HelloWorld -o HelloWorld $ cd HelloWorld $ echo "HELLO=World" > .env $ echo 'Console.WriteLine($"Hello {Environment.GetEnvironmentVariable("HELLO")}");' > Program.cs $ dotenvx run -- dotnet run Hello World
-
Bash 🖥️
$ echo "HELLO=World" > .env $ dotenvx run --quiet -- sh -c 'echo Hello $HELLO' Hello World
-
Cron ⏰
# run every day at 8am 0 8 * * * dotenvx run -- /path/to/myscript.sh
-
Frameworks ▲
$ dotenvx run -- next dev $ dotenvx run -- npm start $ dotenvx run -- bin/rails s $ dotenvx run -- php artisan serve
see framework guides
-
Docker 🐳
$ docker run -it --rm -v $(pwd):/app dotenv/dotenvx run -- node index.js
Or in any image:
FROM node:latest RUN echo "HELLO=World" > .env && echo "console.log('Hello ' + process.env.HELLO)" > index.js RUN curl -fsS https://dotenvx.sh/ | sh CMD ["dotenvx", "run", "--", "echo", "Hello $HELLO"]
see docker guide
-
CI/CDs 🐙
name: build on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 16 - run: curl -fsS https://dotenvx.sh/ | sh - run: dotenvx run -- node build.js env: DOTENV_KEY: ${{ secrets.DOTENV_KEY }}
-
Platforms
# heroku heroku buildpacks:add https://github.com/dotenvx/heroku-buildpack-dotenvx # docker RUN curl -fsS https://dotenvx.sh/ | sh # vercel npm install @dotenvx/dotenvx --save
see platform guides
-
Process Managers
// pm2 "scripts": { "start": "dotenvx run -- pm2-runtime start ecosystem.config.js --env production" },
-
npx
# alternatively use npx $ npx @dotenvx/dotenvx run -- node index.js $ npx @dotenvx/dotenvx run -- next dev $ npx @dotenvx/dotenvx run -- npm start
-
npm
$ npm install @dotenvx/dotenvx --save
{ "scripts": { "start": "./node_modules/.bin/dotenvx run -- node index.js" }, "dependencies": { "@dotenvx/dotenvx": "^0.5.0" } }
$ npm run start > start > ./node_modules/.bin/dotenvx run -- node index.js [dotenvx][info] loading env (1) from .env Hello World
-
Git
# use as a git submodule $ git dotenvx run -- node index.js $ git dotenvx run -- next dev $ git dotenvx run -- npm start
-
Variable Expansion
Reference and expand variables already on your machine for use in your .env file.
# .env USERNAME="username" DATABASE_URL="postgres://${USERNAME}@localhost/my_database"
// index.js console.log('DATABASE_URL', process.env.DATABASE_URL)
$ dotenvx run --debug -- node index.js [dotenvx@0.14.1] injecting env (2) from .env DATABASE_URL postgres://username@localhost/my_database
-
Command Substitution
Add the output of a command to one of your variables in your .env file.
# .env DATABASE_URL="postgres://$(whoami)@localhost/my_database"
// index.js console.log('DATABASE_URL', process.env.DATABASE_URL)
$ dotenvx run --debug -- node index.js [dotenvx@0.14.1] injecting env (1) from .env DATABASE_URL postgres://yourusername@localhost/my_database
Create a
.env.production
file and use-f
to load it. It's straightforward, yet flexible.
$ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.production -- node index.js
[dotenvx][info] loading env (1) from .env.production
Hello production
> ^^
More examples
-
multiple `.env` files
$ echo "HELLO=local" > .env.local $ echo "HELLO=World" > .env $ dotenvx run -f .env.local -f .env -- node index.js [dotenvx][info] loading env (1) from .env.local,.env Hello local
-
`--overload` flag
$ echo "HELLO=local" > .env.local $ echo "HELLO=World" > .env $ dotenvx run -f .env.local -f .env --overload -- node index.js [dotenvx][info] loading env (1) from .env.local,.env Hello World
-
`--verbose` flag
$ echo "HELLO=production" > .env.production $ dotenvx run -f .env.production --verbose -- node index.js [dotenvx][verbose] injecting env from /path/to/.env.production [dotenvx][verbose] HELLO set [dotenvx][info] loading env (1) from .env.production Hello production
-
`--debug` flag
$ echo "HELLO=production" > .env.production $ dotenvx run -f .env.production --debug -- node index.js [dotenvx][debug] configuring options [dotenvx][debug] {"envFile":[".env.production"]} [dotenvx][verbose] injecting env from /path/to/.env.production [dotenvx][debug] reading env from /path/to/.env.production [dotenvx][debug] parsing env from /path/to/.env.production [dotenvx][debug] {"HELLO":"production"} [dotenvx][debug] writing env from /path/to/.env.production [dotenvx][verbose] HELLO set [dotenvx][debug] HELLO set to production [dotenvx][info] loading env (1) from .env.production Hello production
-
`--quiet` flag
Use
--quiet
to suppress all output (except errors).$ echo "HELLO=production" > .env.production $ dotenvx run -f .env.production --quiet -- node index.js Hello production
-
`--log-level` flag
Set
--log-level
to whatever you wish. For example, to supress warnings (risky), set log level toerror
:$ echo "HELLO=production" > .env.production $ dotenvx run -f .env.production --log-level=error -- node index.js Hello production
Available log levels are
error, warn, info, verbose, debug, silly
-
`--convention` flag
Want to load envs conveniently usng the same convention as Next.js? Set
--convention
tonextjs
:$ echo "HELLO=development local" > .env.development.local $ echo "HELLO=local" > .env.local $ echo "HELLO=development" > .env.development $ echo "HELLO=env" > .env $ dotenvx run --convention=nextjs -- node index.js Hello development local
See next.js environment variable load order
(more conventions available upon request)
Add encryption to your
.env
files with a single command. Pass the--encrypt
flag.
$ dotenvx set HELLO World --encrypt
set HELLO with encryption (.env)
A
DOTENV_PUBLIC_KEY
(encryption key) and aDOTENV_PRIVATE_KEY
(decryption key) are generated using the same public-key cryptography as Bitcoin.
More examples
-
`.env`
$ dotenvx set HELLO World --encrypt $ echo "console.log('Hello ' + process.env.HELLO)" > index.js $ dotenvx run -- node index.js [dotenvx] injecting env (2) from .env Hello World
-
`.env.production`
$ dotenvx set HELLO Production --encrypt -f .env.production $ echo "console.log('Hello ' + process.env.HELLO)" > index.js $ DOTENV_PRIVATE_KEY_PRODUCTION="<.env.production private key>" dotenvx run -- node index.js [dotenvx] injecting env (2) from .env.production Hello Production
Note the
DOTENV_PRIVATE_KEY_PRODUCTION
ends with_PRODUCTION
. This instructsdotenvx run
to load the.env.production
file. -
`.env.ci`
$ dotenvx set HELLO Ci --encrypt -f .env.ci $ echo "console.log('Hello ' + process.env.HELLO)" > index.js $ DOTENV_PRIVATE_KEY_CI="<.env.ci private key>" dotenvx run -- node index.js [dotenvx] injecting env (2) from .env.ci Hello Ci
Note the
DOTENV_PRIVATE_KEY_CI
ends with_CI
. This instructsdotenvx run
to load the.env.ci
file. See the pattern? -
combine multiple encrypted .env files
$ dotenvx set HELLO World --encrypt -f .env $ dotenvx set HELLO Production --encrypt -f .env.production $ echo "console.log('Hello ' + process.env.HELLO)" > index.js $ DOTENV_PRIVATE_KEY="<.env private key>" DOTENV_PRIVATE_KEY_PRODUCTION="<.env.production private key>" dotenvx run -- node index.js [dotenvx] injecting env (3) from .env, .env.production Hello World
Note the
DOTENV_PRIVATE_KEY
instructsdotenvx run
to load the.env
file and theDOTENV_PRIVATE_KEY_PRODUCTION
instructs it to load the.env.production
file. See the pattern? -
other curves
secp256k1
is a well-known and battle tested curve, in use with Bitcoin and other cryptocurrencies, but we are open to adding support for more curves.If your organization's compliance department requires NIST approved curves or other curves like
curve25519
, please reach out at security@dotenvx.com.
Keep your
.env
files safe
dotenvx genexample
– generate.env.example
filedotenvx gitignore
– gitignore your.env
filesdotenvx prebuild
– prevent.env
files from being built into your docker containerdotenvx precommit
– prevent.env
files from being committed to codedotenvx scan
– scan for leaked secrets in code
Convenience
dotenvx get
– return a single environment variabledotenvx set
– set a single environment variabledotenvx ls
– list all .env files in your repodotenvx status
– compare your .env* content(s) to your .env.vault decrypted content(s)dotenvx settings
– print current dotenvx settings
You are using Node 20 or greater and it adds a differing implementation of --env-file
flag support. Rather than warn on a missing .env
file (like dotenv has historically done), it raises an error: node: .env: not found
.
This fix is easy. Replace --env-file
with -f
.
# from this:
./node_modules/.bin/dotenvx run --env-file .env -- yourcommand
# to this:
./node_modules/.bin/dotenvx run -f .env -- yourcommand
I've decided we should sunset it as a technological solution to this.
The .env.vault
file got us far, but it had limitations such as:
- Pull Requests - it was difficult to tell which key had been changed
- Security - there was no mechanism to give a teammate the ability to encrypt without also giving them the ability to decrypt. Sometimes you just want to let a contractor encrypt a new value, but you don't want them to know the rest of the secrets.
- Conceptual - it takes more mental energy to understand the
.env.vault
format. Encrypted values inside a.env
file is easier to quickly grasp. - Combining Multiple Files - there was simply no mechanism to do this well with the
.env.vault
file format.
That said, the .env.vault
tooling will still stick around for at least 1 year under dotenvx vault
parent command. I'm still using it in projects as are many thousands of other people.
Yes. Working on this soon.
You can fork this repo and create pull requests or if you have questions or feedback:
- github.com/dotenvx/dotenvx - bugs and discussions
- @dotenvx 𝕏 (DMs are open)