Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
@dotenvx/dotenvx
Advanced tools
a better dotenvโfrom the creator of dotenv
.
ย
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 - unlocks dotenv for any language, framework, or platform!
curl -L -o dotenvx.tar.gz "https://github.com/dotenvx/dotenvx/releases/latest/download/dotenvx-$(uname -s)-$(uname -m).tar.gz"
tar -xzf dotenvx.tar.gz
./dotenvx help
ย
winget install dotenvx
dotenvx help
ย
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ node index.js
Hello undefined # without dotenvx
$ dotenvx run -- node index.js
Hello World # with dotenvx
> :-D
More examples
// 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
$ 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
[!WARNING] Some of you are attempting to use the npm module directly with
deno run
. Don't, because deno currently has incomplete support for these encryption ciphers.$ deno run -A npm:@dotenvx/dotenvx encrypt Unknown cipher
Instead, use
dotenvx
as designed, by installing the cli as a binary - via curl, brew, etc.
$ 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
$ echo "HELLO=World" > .env
$ echo 'import os;print("Hello " + os.getenv("HELLO", ""))' > index.py
$ dotenvx run -- python3 index.py
Hello World
$ echo "HELLO=World" > .env
$ echo '<?php echo "Hello {$_SERVER["HELLO"]}\n";' > index.php
$ dotenvx run -- php index.php
Hello World
$ echo "HELLO=World" > .env
$ echo 'puts "Hello #{ENV["HELLO"]}"' > index.rb
$ dotenvx run -- ruby index.rb
Hello World
$ 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
$ 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
$ 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
$ echo "HELLO=World" > .env
$ echo '(println "Hello" (System/getenv "HELLO"))' > index.clj
$ dotenvx run -- clojure -M index.clj
Hello World
$ echo "HELLO=World" > .env
$ echo 'fun main() { val hello = System.getenv("HELLO") ?: ""; println("Hello $hello") }' > index.kt
$ kotlinc index.kt -include-runtime -d index.jar
$ dotenvx run -- java -jar index.jar
Hello World
$ dotnet new console -n HelloWorld -o HelloWorld
$ cd HelloWorld
$ echo "HELLO=World" | Out-File -FilePath .env -Encoding utf8
$ echo 'Console.WriteLine($"Hello {Environment.GetEnvironmentVariable("HELLO")}");' > Program.cs
$ dotenvx run -- dotnet run
Hello World
$ echo "HELLO=World" > .env
$ dotenvx run --quiet -- sh -c 'echo Hello $HELLO'
Hello World
$ echo "HELLO=World" > .env
$ dotenvx run --quiet -- sh -c 'echo Hello $HELLO'
Hello World
# run every day at 8am
0 8 * * * dotenvx run -- /path/to/myscript.sh
$ dotenvx run -- next dev
$ dotenvx run -- npm start
$ dotenvx run -- bin/rails s
$ dotenvx run -- php artisan serve
see framework guides
$ 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/install.sh | sh
CMD ["dotenvx", "run", "--", "echo", "Hello $HELLO"]
see docker guide
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/install.sh | sh
- run: dotenvx run -- node build.js
env:
DOTENV_KEY: ${{ secrets.DOTENV_KEY }}
# heroku
heroku buildpacks:add https://github.com/dotenvx/heroku-buildpack-dotenvx
# docker
RUN curl -fsS https://dotenvx.sh/install.sh | sh
# vercel
npm install @dotenvx/dotenvx --save
see platform guides
// pm2
"scripts": {
"start": "dotenvx run -- pm2-runtime start ecosystem.config.js --env production"
},
# alternatively use npx
$ npx @dotenvx/dotenvx run -- node index.js
$ npx @dotenvx/dotenvx run -- next dev
$ npx @dotenvx/dotenvx run -- npm start
$ 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
# use dotenvx with asdf
$ asdf plugin add dotenvx
$ asdf install dotenvx latest
thank you @jgburet of Paris ๐ซ๐ท
# use as a git submodule
$ git dotenvx run -- node index.js
$ git dotenvx run -- next dev
$ git dotenvx run -- npm start
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
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
$ 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
$ 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
$ 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
$ 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
Use --quiet
to suppress all output (except errors).
$ echo "HELLO=production" > .env.production
$ dotenvx run -f .env.production --quiet -- node index.js
Hello production
Set --log-level
to whatever you wish. For example, to supress warnings (risky), set log level to error
:
$ 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
Load envs using Next.js' convention. Set --convention
to nextjs
:
$ 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
(more conventions available upon request)
ย
Add encryption to your
.env
files with a single command. Usedotenvx encrypt
.
$ dotenvx encrypt
โ encrypted (.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
$ echo "HELLO=World" > .env
$ dotenvx encrypt
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -- node index.js
[dotenvx] injecting env (2) from .env
Hello World
$ echo "HELLO=Production" > .env.production
$ dotenvx 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 instructs dotenvx run
to load the .env.production
file.
$ echo "HELLO=Ci" > .env.ci
$ dotenvx 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 instructs dotenvx run
to load the .env.ci
file. See the pattern?
$ dotenvx set HELLO World -f .env
$ dotenvx set HELLO Production -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
instructs dotenvx run
to load the .env
file and the DOTENV_PRIVATE_KEY_PRODUCTION
instructs it to load the .env.production
file. See the pattern?
$ mkdir app1
$ mkdir app2
$ dotenvx set HELLO app1 -f app1/.env.ci
$ dotenvx set HELLO app2 -f app2/.env.ci
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ DOTENV_PRIVATE_KEY_CI="<app1/privat ci key>,<app2/private ci key>" dotenvx run -f app1/.env.ci -f app2/.env.ci -- node index.js
[dotenvx] injecting env (2) from app1/.env.ci,app2/.env.ci
Hello app1
$ DOTENV_PRIVATE_KEY_CI="<app1/privat ci key>,<app2/private ci key>" dotenvx run -f app1/.env.ci -f app2/.env.ci --overload -- node index.js
[dotenvx] injecting env (2) from app1/.env.ci,app2/.env.ci
Hello app2
Note the DOTENV_PRIVATE_KEY_CI
(and any DOTENV_PRIVATE_KEY*
) can take multiple private keys by simply comma separating them.
$ echo "HELLO=World" > .env
$ dotenvx encrypt --stdout
$ dotenvx encrypt --stdout > .env.encrypted
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.
ย
Become a
dotenvx
power user.
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] injecting env (2) from .env
DATABASE_URL postgres://username@localhost/my_database
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] injecting env (1) from .env
DATABASE_URL postgres://yourusername@localhost/my_database
Prevent your shell from expanding inline $VARIABLES
before dotenvx has a chance to inject it. Use a subshell.
$ dotenvx run --env="HELLO=World" -- sh -c 'echo Hello $HELLO'
Hello World
Compose multiple .env
files for environment variables loading, as you need.
$ echo "HELLO=local" > .env.local
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.local -f .env -- node index.js
[dotenvx] injecting env (1) from .env.local, .env
Hello local
Set environment variables as a simple KEY=value
string pair.
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run --env HELLO=String -f .env -- node index.js
[dotenvx] injecting env (1) from .env, and --env flag
Hello String
Override existing env variables. These can be variables already on your machine or variables loaded as files consecutively. The last variable seen will 'win'.
$ echo "HELLO=local" > .env.local
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.local -f .env --overload -- node index.js
[dotenvx] injecting env (1) from .env.local, .env
Hello World
Decrypt your encrypted .env
by setting DOTENV_PRIVATE_KEY
before dotenvx run
.
$ touch .env
$ dotenvx set HELLO encrypted
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
# check your .env.keys files for your privateKey
$ DOTENV_PRIVATE_KEY="122...0b8" dotenvx run -- node index.js
[dotenvx] injecting env (2) from .env
Hello encrypted
Decrypt your encrypted .env.production
by setting DOTENV_PRIVATE_KEY_PRODUCTION
before dotenvx run
. Alternatively, this can be already set on your server or cloud provider.
$ touch .env.production
$ dotenvx set HELLO "production encrypted" -f .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
# check .env.keys for your privateKey
$ DOTENV_PRIVATE_KEY_PRODUCTION="122...0b8" dotenvx run -- node index.js
[dotenvx] injecting env (2) from .env.production
Hello production encrypted
Note the DOTENV_PRIVATE_KEY_PRODUCTION
ends with _PRODUCTION
. This instructs dotenvx run to load the .env.production
file.
Decrypt your encrypted .env.ci
by setting DOTENV_PRIVATE_KEY_CI
before dotenvx run
. Alternatively, this can be already set on your server or cloud provider.
$ touch .env.ci
$ dotenvx set HELLO "ci encrypted" -f .env.ci
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
# check .env.keys for your privateKey
$ DOTENV_PRIVATE_KEY_CI="122...0b8" dotenvx run -- node index.js
[dotenvx] injecting env (2) from .env.ci
Hello ci encrypted
Note the DOTENV_PRIVATE_KEY_CI
ends with _CI
. This instructs dotenvx run to load the .env.ci
file. See the pattern?
Decrypt your encrypted .env
and .env.production
files by setting DOTENV_PRIVATE_KEY
and DOTENV_PRIVATE_KEY_PRODUCTION
before dotenvx run
.
$ touch .env
$ touch .env.production
$ dotenvx set HELLO encrypted
$ dotenvx set HELLO "production encrypted" -f .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
# check .env.keys for your privateKeys
$ DOTENV_PRIVATE_KEY="122...0b8" DOTENV_PRIVATE_KEY_PRODUCTION="122...0b8" dotenvx run -- node index.js
[dotenvx] injecting env (3) from .env, .env.production
Hello encrypted
$ DOTENV_PRIVATE_KEY_PRODUCTION="122...0b8" DOTENV_PRIVATE_KEY="122...0b8" dotenvx run -- node index.js
[dotenvx] injecting env (3) from .env.production, .env
Hello production encrypted
Compose any encrypted files you want this way. As long as a DOTENV_PRIVATE_KEY_${environment}
is set, the values from .env.${environment}
will be decrypted at runtime.
Set log level to verbose
. (log levels)
$ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.production --verbose -- node index.js
loading env from .env.production (/path/to/.env.production)
HELLO set
[dotenvx] injecting env (1) from .env.production
Hello production
Set log level to debug
. (log levels)
$ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.production --debug -- node index.js
process command [node index.js]
options: {"env":[],"envFile":[".env.production"]}
loading env from .env.production (/path/to/.env.production)
{"HELLO":"production"}
HELLO set
HELLO set to production
[dotenvx] injecting env (1) from .env.production
executing process command [node index.js]
expanding process command to [/opt/homebrew/bin/node index.js]
Hello production
Use --quiet
to suppress all output (except errors). (log levels)
$ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.production --quiet -- node index.js
Hello production
Set --log-level
to whatever you wish. For example, to supress warnings (risky), set log level to error
:
$ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.production --log-level=error -- node index.js
Hello production
Available log levels are error, warn, info, verbose, debug, silly
(source)
Exit with code 1
if any errors are encountered - like a missing .env file or decryption failure.
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.missing --strict -- node index.js
[MISSING_ENV_FILE] missing .env.missing file (/path/to/.env.missing)
[MISSING_ENV_FILE] ? add one with [echo "HELLO=World" > .env.missing]
This can be useful in ci
scripts where you want to fail the ci if your .env
file could not be decrypted at runtime.
Ignore errors like MISSING_ENV_FILE
.
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -f .env.missing --ignore=MISSING_ENV_FILE -- node index.js
...
Load envs using Next.js' convention. Set --convention
to nextjs
:
$ echo "HELLO=development local" > .env.development.local
$ echo "HELLO=local" > .env.local
$ echo "HELLO=development" > .env.development
$ echo "HELLO=env" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run --convention=nextjs -- node index.js
[dotenvx] injecting env (1) from .env.development.local, .env.local, .env.development, .env
Hello development local
(more conventions available upon request)
Return a single environment variable's value.
$ echo "HELLO=World" > .env
$ dotenvx get HELLO
World
Return a single environment variable's value from a specific .env
file.
$ echo "HELLO=World" > .env
$ echo "HELLO=production" > .env.production
$ dotenvx get HELLO -f .env.production
production
Return a single environment variable's value from a --env
string.
$ dotenvx get HELLO --env HELLO=String -f .env.production
String
Return a single environment variable's value where each found value is overloaded.
$ echo "HELLO=World" > .env
$ echo "HELLO=production" > .env.production
$ dotenvx get HELLO -f .env.production --env HELLO=String -f .env --overload
World
Exit with code 1
if any errors are encountered - like a missing key, missing .env file, or decryption failure.
$ dotenvx get DOES_NOT_EXIST --strict
[MISSING_KEY] missing DOES_NOT_EXIST key
Return a single environment variable's value using Next.js' convention. Set --convention
to nextjs
:
$ echo "HELLO=development local" > .env.development.local
$ echo "HELLO=local" > .env.local
$ echo "HELLO=development" > .env.development
$ echo "HELLO=env" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx get HELLO --convention=nextjs
development local
Return a json response of all key/value pairs in a .env
file.
$ echo "HELLO=World" > .env
$ dotenvx get
{"HELLO":"World"}
Return a shell formatted response of all key/value pairs in a .env
file.
$ echo "HELLO=World" > .env
$ echo "KEY=value" >> .env
$ dotenvx get --format shell
HELLO=World KEY=value
This can be useful when combined with env
on the command line.
$ echo "console.log('Hello ' + process.env.KEY + ' ' + process.env.HELLO)" > index.js
$ env $(dotenvx get --format=shell) node index.js
Hello value World
or with export
.
$ echo "console.log('Hello ' + process.env.KEY + ' ' + process.env.HELLO)" > index.js
$ export $(dotenvx get --format=shell)
$ node index.js
Hello value World
Return an eval
-ready shell formatted response of all key/value pairs in a .env
file.
$ echo "HELLO=World" > .env
$ echo "KEY=value" >> .env
$ dotenvx get --format eval
HELLO="World"
KEY="value"
Note that this exports newlines and quoted strings.
This can be useful for more complex .env values (spaces, escaped characters, quotes, etc) combined with eval
on the command line.
$ echo "console.log('Hello ' + process.env.KEY + ' ' + process.env.HELLO)" > index.js
$ eval $(dotenvx get --format=eval) node index.js
Hello value World
Be careful with eval
as it allows for arbitrary execution of commands. Prefer dotenvx run --
but in some cases eval
is a sharp knife that is useful to have.
Return preset machine envs as well.
$ echo "HELLO=World" > .env
$ dotenvx get --all
{"PWD":"/some/file/path","USER":"username","LIBRARY_PATH":"/usr/local/lib", ..., "HELLO":"World"}
Make the output more readable - pretty print it.
$ echo "HELLO=World" > .env
$ dotenvx get --all --pretty-print
{
"PWD": "/some/filepath",
"USER": "username",
"LIBRARY_PATH": "/usr/local/lib",
...,
"HELLO": "World"
}
Set an encrypted key/value (on by default).
$ touch .env
$ dotenvx set HELLO World
set HELLO with encryption (.env)
Set an (encrypted) key/value for another .env
file.
$ touch .env.production
$ dotenvx set HELLO production -f .env.production
set HELLO with encryption (.env.production)
Set a value containing spaces.
$ touch .env.ci
$ dotenvx set HELLO "my ci" -f .env.ci
set HELLO with encryption (.env.ci)
If your value starts with a dash (-
), then place two dashes instructing the cli that there are no more flag arguments.
$ touch .env.ci
$ dotenvx set HELLO -f .env.ci -- "- + * รท"
set HELLO with encryption (.env.ci)
Set a plaintext key/value.
$ touch .env
$ dotenvx set HELLO World --plain
set HELLO (.env)
Encrypt the contents of a .env
file to an encrypted .env
file.
$ echo "HELLO=World" > .env
$ dotenvx encrypt
โ encrypted (.env)
โ key added to .env.keys (DOTENV_PRIVATE_KEY)
โฎ next run [dotenvx ext gitignore --pattern .env.keys] to gitignore .env.keys
โฎ next run [DOTENV_PRIVATE_KEY='122...0b8' dotenvx run -- yourcommand] to test decryption locally
Encrypt the contents of a specified .env
file to an encrypted .env
file.
$ echo "HELLO=World" > .env
$ echo "HELLO=Production" > .env.production
$ dotenvx encrypt -f .env.production
โ encrypted (.env.production)
โ key added to .env.keys (DOTENV_PRIVATE_KEY_PRODUCTION)
โฎ next run [dotenvx ext gitignore --pattern .env.keys] to gitignore .env.keys
โฎ next run [DOTENV_PRIVATE_KEY='bff...bc4' dotenvx run -- yourcommand] to test decryption locally
Specify the key(s) to encrypt by passing --key
.
$ echo "HELLO=World\nHELLO2=Universe" > .env
$ dotenvx encrypt -k HELLO2
โ encrypted (.env)
Even specify a glob pattern.
$ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt -k "HE*"
โ encrypted (.env)
Specify the key(s) to NOT encrypt by passing --exclude-key
.
$ echo "HELLO=World\nHELLO2=Universe" > .env
$ dotenvx encrypt -ek HELLO
โ encrypted (.env)
Even specify a glob pattern.
$ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt -ek "HO*"
โ encrypted (.env)
Encrypt the contents of a .env
file and send to stdout.
$ echo "HELLO=World" > .env
$ dotenvx encrypt --stdout
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY="034af93e93708b994c10f236c96ef88e47291066946cce2e8d98c9e02c741ced45"
# .env
HELLO="encrypted:BDqDBibm4wsYqMpCjTQ6BsDHmMadg9K3dAt+Z9HPMfLEIRVz50hmLXPXRuDBXaJi/LwWYEVUNiq0HISrslzQPaoyS8Lotg3gFWJTsNCdOWnqpjF2xNUX2RQiP05kAbEXM6MWVjDr"
or send to a file:
$ echo "HELLO=World" > .env
$ dotenvx encrypt --stdout > somefile.txt
Decrypt the contents of an encrypted .env
file to an unencrypted .env
file.
$ echo "HELLO=World" > .env
$ dotenvx encrypt
โ encrypted (.env)
$ dotenvx decrypt
โ decrypted (.env)
Decrypt the contents of a specified encrypted .env
file to an unencrypted .env
file.
$ echo "HELLO=World" > .env
$ echo "HELLO=Production" > .env.production
$ dotenvx encrypt -f .env.production
โ encrypted (.env.production)
$ dotenvx decrypt -f .env.production
โ decrypted (.env.production)
Decrypt the contents of a specified key inside an encrypted .env
file.
$ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
โ encrypted (.env)
$ dotenvx decrypt -k HELLO
โ decrypted (.env)
Even specify a glob pattern.
$ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
โ encrypted (.env)
$ dotenvx decrypt -k "HE*"
โ encrypted (.env)
Decrypt the contents inside an encrypted .env
file except for an exluded key.
$ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
โ encrypted (.env)
$ dotenvx decrypt -ek HOLA
โ decrypted (.env)
Even specify a glob pattern.
$ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
โ encrypted (.env)
$ dotenvx decrypt -ek "HO*"
โ encrypted (.env)
Decrypt the contents of an encrypted .env
file and send to stdout.
$ dotenvx decrypt --stdout
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY="034af93e93708b994c10f236c96ef88e47291066946cce2e8d98c9e02c741ced45"
# .env
HELLO="World"
or send to a file:
$ dotenvx decrypt --stdout > somefile.txt
Print public/private keys for .env
file.
$ echo "HELLO=World" > .env
$ dotenvx encrypt
$ dotenvx keypair
{"DOTENV_PUBLIC_KEY":"<publicKey>","DOTENV_PRIVATE_KEY":"<privateKey>"}
Print public/private keys for .env.production
file.
$ echo "HELLO=Production" > .env.production
$ dotenvx encrypt -f .env.production
$ dotenvx keypair -f .env.production
{"DOTENV_PUBLIC_KEY_PRODUCTION":"<publicKey>","DOTENV_PRIVATE_KEY_PRODUCTION":"<privateKey>"}
Print specific keypair for .env
file.
$ echo "HELLO=World" > .env
$ dotenvx encrypt
$ dotenvx keypair DOTENV_PRIVATE_KEY
<privateKey>
Print a shell formatted reponse of public/private keys.
$ echo "HELLO=World" > .env
$ dotenx encrypt
$ dotenvx keypair --format shell
DOTENV_PUBLIC_KEY=<publicKey> DOTENV_PRIVATE_KEY=<privateKey>
Print all .env
files in a tree structure.
$ touch .env
$ touch .env.production
$ mkdir -p apps/backend
$ touch apps/backend/.env
$ dotenvx ls
โโ .env.production
โโ .env
โโ apps
โโ backend
โโ .env
Print all .env
files inside a specified path to a directory.
$ touch .env
$ touch .env.production
$ mkdir -p apps/backend
$ touch apps/backend/.env
$ dotenvx ls apps/backend
โโ .env
Glob .env
filenames matching a wildcard.
$ touch .env
$ touch .env.production
$ mkdir -p apps/backend
$ touch apps/backend/.env
$ touch apps/backend/.env.prod
$ dotenvx ls -f **/.env.prod*
โโ .env.production
โโ apps
โโ backend
โโ .env.prod
Glob .env
filenames excluding a wildcard.
$ touch .env
$ touch .env.production
$ mkdir -p apps/backend
$ touch apps/backend/.env
$ touch apps/backend/.env.prod
$ dotenvx ls -ef '**/.env.prod*'
โโ .env
โโ apps
โโ backend
โโ .env
Output help for dotenvx
.
$ dotenvx help
Usage: dotenvx run -- yourcommand
a better dotenvโfrom the creator of `dotenv`
Options:
-l, --log-level <level> set log level (default: "info")
-q, --quiet sets log level to error
-v, --verbose sets log level to verbose
-d, --debug sets log level to debug
-V, --version output the version number
-h, --help display help for command
Commands:
run inject env at runtime [dotenvx run -- yourcommand]
get [KEY] return a single environment variable
set <KEY> <value> set a single environment variable
encrypt convert .env file(s) to encrypted .env file(s)
decrypt convert encrypted .env file(s) to plain .env file(s)
keypair [KEY] print public/private keys for .env file(s)
ls [directory] print all .env files in a tree structure
Advanced:
pro ๐ pro
ext ๐ extensions
You can get more detailed help per command with dotenvx help COMMAND
.
$ dotenvx help run
Usage: @dotenvx/dotenvx run [options]
inject env at runtime [dotenvx run -- yourcommand]
Options:
-e, --env <strings...> environment variable(s) set as string (example: "HELLO=World") (default: [])
-f, --env-file <paths...> path(s) to your env file(s) (default: [])
-fv, --env-vault-file <paths...> path(s) to your .env.vault file(s) (default: [])
-o, --overload override existing env variables
--convention <name> load a .env convention (available conventions: ['nextjs'])
-h, --help display help for command
Examples:
$ dotenvx run -- npm run dev
$ dotenvx run -- flask --app index run
$ dotenvx run -- php artisan serve
$ dotenvx run -- bin/rails s
Try it:
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -- node index.js
[dotenvx] injecting env (1) from .env
Hello World
Check current version of dotenvx
.
$ dotenvx --version
X.X.X
In one command, generate a .env.example
file from your current .env
file contents.
$ echo "HELLO=World" > .env
$ dotenvx ext genexample
โ updated .env.example (1)
# .env.example
HELLO=""
Pass multiple .env
files to generate your .env.example
file from the combination of their contents.
$ echo "HELLO=World" > .env
$ echo "DB_HOST=example.com" > .env.production
$ dotenvx ext genexample -f .env -f .env.production
โ updated .env.example (2)
# .env.example
HELLO=""
DB_HOST=""
Generate a .env.example
file inside the specified directory. Useful for monorepos.
$ echo "HELLO=World" > .env
$ mkdir -p apps/backend
$ echo "HELLO=Backend" > apps/backend/.env
$ dotenvx ext genexample apps/backend
โ updated .env.example (1)
# apps/backend/.env.example
HELLO=""
Gitignore your .env
files.
$ dotenvx ext gitignore
โ ignored .env* (.gitignore)
Gitignore specific pattern(s) of .env
files.
$ dotenvx ext gitignore --pattern .env.keys
โ ignored .env.keys (.gitignore)
Prevent .env
files from being committed to code.
$ dotenvx ext precommit
[dotenvx][precommit] .env files (1) protected (encrypted or gitignored)
Install a shell script to .git/hooks/pre-commit
to prevent accidentally committing any .env
files to source control.
$ dotenvx ext precommit --install
[dotenvx][precommit] dotenvx ext precommit installed [.git/hooks/pre-commit]
Prevent .env
files from being built into your docker containers.
Add it to your Dockerfile
.
# Dockerfile
RUN curl -fsS https://dotenvx.sh | sh
...
RUN dotenvx ext prebuild
CMD ["dotenvx", "run", "--", "node", "index.js"]
Use gitleaks under the hood to scan for possible secrets in your code.
$ dotenvx ext scan
โ
โโฒ
โ โ
โ โ
โ gitleaks
100 commits scanned.
no leaks found
Use directly in node.js code.
# .env
HELLO="World"
// index.js
require('@dotenvx/dotenvx').config()
console.log(`Hello ${process.env.HELLO}`)
$ node index.js
[dotenvx@1.24.5] injecting env (1) from .env
Hello World
It defaults to looking for a .env
file.
Specify path(s) to multiple .env files.
# .env.local
HELLO="Me"
# .env
HELLO="World"
// index.js
require('@dotenvx/dotenvx').config({path: ['.env.local', '.env']})
console.log(`Hello ${process.env.HELLO}`)
$ node index.js
[dotenvx@1.24.5] injecting env (1) from .env.local, .env
Hello Me
Use overload
to overwrite the prior set value.
# .env.local
HELLO="Me"
# .env
HELLO="World"
// index.js
require('@dotenvx/dotenvx').config({path: ['.env.local', '.env'], overload: true})
console.log(`Hello ${process.env.HELLO}`)
$ node index.js
[dotenvx@1.24.5] injecting env (1) from .env.local, .env
Hello World
Use strict
to throw if an error is encountered - like a missing .env file.
# .env
HELLO="World"
// index.js
require('@dotenvx/dotenvx').config({path: ['.env.missing', '.env'], strict: true})
console.log(`Hello ${process.env.HELLO}`)
$ node index.js
Error: [MISSING_ENV_FILE] missing .env.missing file (/path/to/.env.missing)
Use ignore
to suppress specific errors like MISSING_ENV_FILE
.
# .env
HELLO="World"
// index.js
require('@dotenvx/dotenvx').config({path: ['.env.missing', '.env'], ignore: ['MISSING_ENV_FILE']})
console.log(`Hello ${process.env.HELLO}`)
$ node index.js
[dotenvx@1.24.5] injecting env (1) from .env.local, .env
Hello World
Parse a .env
string directly in node.js code.
// index.js
const dotenvx = require('@dotenvx/dotenvx')
const src = 'HELLO=World'
const parsed = dotenvx.parse(src)
console.log(`Hello ${parsed.HELLO}`)
$ node index.js
Hello World
Sometimes, you want to run parse
without it accessing process.env
. (You can pass a fake processEnv this way as well - sometimes useful.)
// index.js
const dotenvx = require('@dotenvx/dotenvx')
const src = 'USER=Me'
const parsed = dotenvx.parse(src, { processEnv: {} })
console.log(`Hello ${parsed.USER}`)
$ node index.js
Hello Me
Decrypt an encrypted .env
string with privateKey
.
// index.js
const dotenvx = require('@dotenvx/dotenvx')
const src = 'HELLO="encrypted:BE9Y7LKANx77X1pv1HnEoil93fPa5c9rpL/1ps48uaRT9zM8VR6mHx9yM+HktKdsPGIZELuZ7rr2mn1gScsmWitppAgE/1lVprNYBCqiYeaTcKXjDUXU5LfsEsflnAsDhT/kWG1l"'
const parsed = dotenvx.parse(src, { privateKey: 'a4547dcd9d3429615a3649bb79e87edb62ee6a74b007075e9141ae44f5fb412c' })
console.log(`Hello ${parsed.HELLO}`)
$ node index.js
Hello World
ย
Go deeper into using
dotenvx
with detailed framework and platform guides.
ย
node: .env: not found
?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
.env.vault
file?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:
.env.vault
format. Encrypted values inside a .env
file is easier to quickly grasp..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.
.env.vault
file(s) to encrypted .env
files?Run $ dotenvx ext vault migrate
and follow the instructions.
ย
You can fork this repo and create pull requests or if you have questions or feedback:
FAQs
a better dotenvโfrom the creator of `dotenv`
The npm package @dotenvx/dotenvx receives a total of 173,133 weekly downloads. As such, @dotenvx/dotenvx popularity was classified as popular.
We found that @dotenvx/dotenvx demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.