winget install --id=dotenvx.dotenvx -e
a better dotenvโfrom the creator of dotenv
dotenvx is a cross-platform tool designed to simplify environment variable management for developers and DevOps teams. Built by the creator of dotenv
, dotenvx enhances security and flexibility with encrypted variables, multi-environment support, and seamless integration across programming languages and frameworks.
Key Features:
.env
files for development, testing, and production.Audience & Benefit: Ideal for developers working on projects that require secure and efficient management of environment variables. dotenvx helps teams streamline workflows, reduce configuration errors, and enhance security by encrypting sensitive data. It is particularly beneficial for organizations deploying applications across multiple environments or using diverse programming languages.
The tool can be installed via winget on Windows, making it easy to integrate into existing development pipelines.
a secure 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()
// or import '@dotenvx/dotenvx/config' // for esm
console.log(`Hello ${process.env.HELLO}`)
ย
or install globally - unlocks dotenv for any language, framework, or platform!
with curl ๐
curl -sfS https://dotenvx.sh | sh
dotenvx help
ย
with brew ๐บ
brew install dotenvx/brew/dotenvx
dotenvx help
ย
with docker ๐ณ
docker run -it --rm -v $(pwd):/app dotenv/dotenvx help
ย
with github releases ๐
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
ย
or windows ๐ช
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
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
> [!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.
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 ' 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
Clojure ๐ฟ
$ echo "HELLO=World" > .env
$ echo '(println "Hello" (System/getenv "HELLO"))' > index.clj
$ dotenvx run -- clojure -M index.clj
Hello World
Kotlin ๐
$ 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
.NET ๐ต
$ 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
Bash ๐ฅ๏ธ
$ echo "HELLO=World" > .env
$ dotenvx run --quiet -- sh -c 'echo Hello $HELLO'
Hello World
Fish ๐
$ 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/install.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/install.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/install.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@1.X.X] injecting env (1) from .env.production
Hello World
asdf
# use dotenvx with asdf
$ asdf plugin add dotenvx
$ asdf install dotenvx latest
thank you @jgburet of Paris ๐ซ๐ท
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@1.X.X] injecting 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@1.X.X] injecting env (1) from .env.local,.env
Hello local
Note subsequent files do NOT override pre-existing variables defined in previous files or env. This follows historic principle. For example, above local
wins โ from the first file.
--overload
flag
$ echo "HELLO=local" > .env.local
$ echo "HELLO=World" > .env
$ dotenvx run -f .env.local -f .env --overload -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env.local,.env
Hello World
Note that with --overload
subsequent files DO override pre-existing variables defined in previous files.
--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@1.X.X] injecting 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@1.X.X] injecting 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 suppress 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
--convention
flag
Load envs using Next.js' convention or dotenv-flow convention. Set --convention
to nextjs
or flow
:
$ 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
$ dotenvx run --convention=flow -- node index.js
Hello development local
(more conventions available upon request)
ย
> Add encryption to your .env
files with a single command. Use dotenvx encrypt
.
$ dotenvx encrypt
โ encrypted (.env)
> A DOTENV_PUBLIC_KEY
(encryption key) and a DOTENV_PRIVATE_KEY
(decryption key) are generated using the same public-key cryptography as Bitcoin.
More examples
.env
$ echo "HELLO=World" > .env
$ dotenvx encrypt
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ dotenvx run -- node index.js
[dotenvx@1.X.X] injecting env (2) from .env
Hello World
.env.production
$ 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@1.X.X] 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.
.env.ci
$ 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@1.X.X] 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?
combine multiple encrypted .env files
$ 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@1.X.X] 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?
combine multiple encrypted .env files for monorepo
$ 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="," dotenvx run -f app1/.env.ci -f app2/.env.ci -- node index.js
[dotenvx@1.X.X] injecting env (2) from app1/.env.ci,app2/.env.ci
Hello app1
$ DOTENV_PRIVATE_KEY_CI="," dotenvx run -f app1/.env.ci -f app2/.env.ci --overload -- node index.js
[dotenvx@1.X.X] 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.
--stdout
$ echo "HELLO=World" > .env
$ dotenvx encrypt --stdout
$ dotenvx encrypt --stdout > .env.encrypted
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.
ย
> Become a dotenvx
power user.
>
Advanced CLI commands.
run
- 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@1.X.X] injecting env (2) from .env
DATABASE_URL postgres://username@localhost/my_database
run
- Default Values
Use default values when environment variables are unset or empty.
# .env
# Default value syntax: use value if set, otherwise use default
DATABASE_HOST=${DB_HOST:-localhost}
DATABASE_PORT=${DB_PORT:-5432}
# Alternative syntax (no colon): use value if set, otherwise use default
API_URL=${API_BASE_URL-https://api.example.com}
// index.js
console.log('DATABASE_HOST', process.env.DATABASE_HOST)
console.log('DATABASE_PORT', process.env.DATABASE_PORT)
console.log('API_URL', process.env.API_URL)
$ dotenvx run --debug -- node index.js
[dotenvx@1.X.X] injecting env (3) from .env
DATABASE_HOST localhost
DATABASE_PORT 5432
API_URL https://api.example.com
run
- Alternate Values
Use alternate values when environment variables are set and non-empty.
# .env
NODE_ENV=production
# Alternate value syntax: use alternate if set and non-empty, otherwise empty
DEBUG_MODE=${NODE_ENV:+false}
LOG_LEVEL=${NODE_ENV:+error}
# Alternative syntax (no colon): use alternate if set, otherwise empty
CACHE_ENABLED=${NODE_ENV+true}
// index.js
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('DEBUG_MODE', process.env.DEBUG_MODE)
console.log('LOG_LEVEL', process.env.LOG_LEVEL)
console.log('CACHE_ENABLED', process.env.CACHE_ENABLED)
$ dotenvx run --debug -- node index.js
[dotenvx@1.X.X] injecting env (4) from .env
NODE_ENV production
DEBUG_MODE false
LOG_LEVEL error
CACHE_ENABLED true
run
- Interpolation Syntax Summary (Variable Expansion, Default/Alternate Values)
Complete reference for variable interpolation patterns supported by dotenvx:
# .env
DEFINED_VAR=hello
EMPTY_VAR=
# UNDEFINED_VAR is not set
# Default value syntax - use variable if set/non-empty, otherwise use default
TEST1=${DEFINED_VAR:-fallback} # Result: "hello"
TEST2=${EMPTY_VAR:-fallback} # Result: "fallback"
TEST3=${UNDEFINED_VAR:-fallback} # Result: "fallback"
# Default value syntax (no colon) - use variable if set, otherwise use default
TEST4=${DEFINED_VAR-fallback} # Result: "hello"
TEST5=${EMPTY_VAR-fallback} # Result: "" (empty, but set)
TEST6=${UNDEFINED_VAR-fallback} # Result: "fallback"
# Alternate value syntax - use alternate if variable is set/non-empty, otherwise empty
TEST7=${DEFINED_VAR:+alternate} # Result: "alternate"
TEST8=${EMPTY_VAR:+alternate} # Result: "" (empty)
TEST9=${UNDEFINED_VAR:+alternate} # Result: "" (empty)
# Alternate value syntax (no colon) - use alternate if variable is set, otherwise empty
TEST10=${DEFINED_VAR+alternate} # Result: "alternate"
TEST11=${EMPTY_VAR+alternate} # Result: "alternate" (empty but set)
TEST12=${UNDEFINED_VAR+alternate} # Result: "" (empty)
Key differences:
:-
vs -
: The colon makes empty values trigger the fallback:+
vs +
: The colon makes empty values not trigger the alternate-
): Use variable value or fallback+
): Use alternate value or empty stringrun
- 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@1.X.X] injecting env (1) from .env
DATABASE_URL postgres://yourusername@localhost/my_database
run
- Shell Expansion
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
run
- Multiline
Dotenvx supports multiline values. This is particularly useful in conjunction with Docker - which does not support multiline values.
# .env
MULTILINE_PEM="-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNl1tL3QjKp3DZWM0T3u
LgGJQwu9WqyzHKZ6WIA5T+7zPjO1L8l3S8k8YzBrfH4mqWOD1GBI8Yjq2L1ac3Y/
bTdfHN8CmQr2iDJC0C6zY8YV93oZB3x0zC/LPbRYpF8f6OqX1lZj5vo2zJZy4fI/
kKcI5jHYc8VJq+KCuRZrvn+3V+KuL9tF9v8ZgjF2PZbU+LsCy5Yqg1M8f5Jp5f6V
u4QuUoobAgMBAAE=
-----END PUBLIC KEY-----"
// index.js
console.log('MULTILINE_PEM', process.env.MULTILINE_PEM)
$ dotenvx run -- node index.js
MULTILINE_PEM -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNl1tL3QjKp3DZWM0T3u
LgGJQwu9WqyzHKZ6WIA5T+7zPjO1L8l3S8k8YzBrfH4mqWOD1GBI8Yjq2L1ac3Y/
bTdfHN8CmQr2iDJC0C6zY8YV93oZB3x0zC/LPbRYpF8f6OqX1lZj5vo2zJZy4fI/
kKcI5jHYc8VJq+KCuRZrvn+3V+KuL9tF9v8ZgjF2PZbU+LsCy5Yqg1M8f5Jp5f6V
u4QuUoobAgMBAAE=
-----END PUBLIC KEY-----
run
- Contextual Help
Unlike other dotenv libraries, dotenvx attempts to unblock you with contextual help.
For example, when missing a custom .env file:
$ dotenvx run -f .env.missing -- echo $HELLO
[MISSING_ENV_FILE] missing .env.missing file (/Users/scottmotte/Code/dotenvx/playground/apr-16/.env.missing)
[MISSING_ENV_FILE] https://github.com/dotenvx/dotenvx/issues/484 and re-run [dotenvx run -- echo]
or when missing a KEY:
$ echo "HELLO=World" > .env
$ dotenvx get GOODBYE
[MISSING_KEY] missing GOODBYE key
run
- multiple -f
flags
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@1.X.X] injecting env (1) from .env.local, .env
Hello local
Note subsequent files do NOT override pre-existing variables defined in previous files or env. This follows historic principle. For example, above local
wins โ from the first file.
run --env HELLO=String
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@1.X.X] injecting env (1) from .env, and --env flag
Hello String
run --overload
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@1.X.X] injecting env (1) from .env.local, .env
Hello World
Note that with --overload
subsequent files DO override pre-existing variables defined in previous files.
run
- Environment Variable Precedence (Container/Cloud Deployments)
When deploying applications in containers or cloud environments, you often need to override specific environment variables at runtime without modifying committed .env
files. By default, dotenvx follows the historic dotenv principle: environment variables already present take precedence over .env
files.
# .env.prod contains: MODEL_REGISTRY=registry.company.com/models/v1
$ echo "MODEL_REGISTRY=registry.company.com/models/v1" > .env.prod
$ echo "console.log('MODEL_REGISTRY:', process.env.MODEL_REGISTRY)" > app.js
# Without environment variable set - uses .env.prod value
$ dotenvx run -f .env.prod -- node app.js
MODEL_REGISTRY: registry.company.com/models/v1
# With environment variable set (e.g., via Azure Container Service) - environment variable takes precedence
$ MODEL_REGISTRY=registry.azure.com/models/v2 dotenvx run -f .env.prod -- node app.js
MODEL_REGISTRY: registry.azure.com/models/v2
# To force .env.prod to override environment variables, use --overload
$ MODEL_REGISTRY=registry.azure.com/models/v2 dotenvx run -f .env.prod --overload -- node app.js
MODEL_REGISTRY: registry.company.com/models/v1
For container deployments: Set environment variables through your cloud provider's UI/configuration (Azure Container Service, AWS ECS, etc.) to override specific values from committed .env
files without rebuilding your application.
DOTENV_PRIVATE_KEY=key run
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@1.X.X] injecting env (2) from .env
Hello encrypted
DOTENV_PRIVATE_KEY_PRODUCTION=key run
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@1.X.X] 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.
DOTENV_PRIVATE_KEY_CI=key dotenvx run
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@1.X.X] 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?
DOTENV_PRIVATE_KEY=key DOTENV_PRIVATE_KEY_PRODUCTION=key run
- Combine Multiple
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@1.X.X] 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@1.X.X] 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.
run --verbose
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@1.X.X] injecting env (1) from .env.production
Hello production
run --debug
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@1.X.X] injecting env (1) from .env.production
executing process command [node index.js]
expanding process command to [/opt/homebrew/bin/node index.js]
Hello production
run --quiet
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
run --log-level
Set --log-level
to whatever you wish. For example, to suppress 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)
run --strict
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.
run --ignore
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
...
run --convention=nextjs
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@1.X.X] injecting env (1) from .env.development.local, .env.local, .env.development, .env
Hello development local
(more conventions available upon request)
run --convention=flow
Load envs using dotenv-flow's convention. Set --convention
to flow
:
$ echo "HELLO=development local" > .env.development.local
$ echo "HELLO=development" > .env.development
$ echo "HELLO=local" > .env.local
$ echo "HELLO=env" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ NODE_ENV=development dotenvx run --convention=flow -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env.development.local, .env.development, .env.local, .env
Hello development local
Further, we recommend using DOTENV_ENV
over NODE_ENV
โ as dotenvx
works everywhere, not just node.
$ DOTENV_ENV=development dotenvx run --convention=flow -- node index.js
[dotenvx@1.X.X] injecting env (1) from .env.development.local, .env.development, .env.local, .env
Hello development local
run -fk
Specify path to .env.keys
. This is useful with monorepos.
$ mkdir -p apps/app1
$ touch apps/app1/.env
$ dotenvx set HELLO world -fk .env.keys -f apps/app1/.env
$ dotenvx run -fk .env.keys -f apps/app1/.env -- yourcommand
get KEY
Return a single environment variable's value.
$ echo "HELLO=World" > .env
$ dotenvx get HELLO
World
get KEY -f
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
get KEY -fk
Specify path to .env.keys
. This is useful with monorepos.
$ mkdir -p apps/app1
$ touch apps/app1/.env
$ dotenvx set HELLO world -fk .env.keys -f apps/app1/.env
$ dotenvx get HELLO -fk .env.keys -f apps/app1/.env
world
get KEY --env
Return a single environment variable's value from a --env
string.
$ dotenvx get HELLO --env HELLO=String -f .env.production
String
get KEY --overload
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
get KEY --strict
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
get KEY --convention=nextjs
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
get KEY --convention=flow
Return a single environment variable's value using dotenv-flow's convention. Set --convention
to flow
:
$ echo "HELLO=development local" > .env.development.local
$ echo "HELLO=development" > .env.development
$ echo "HELLO=local" > .env.local
$ echo "HELLO=env" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ NODE_ENV=development dotenvx get HELLO --convention=flow
development local
Further, we recommend using DOTENV_ENV
over NODE_ENV
โ as dotenvx
works everywhere, not just node.
$ DOTENV_ENV=development dotenvx get HELLO --convention=flow
development local
get
(json)
Return a json response of all key/value pairs in a .env
file.
$ echo "HELLO=World" > .env
$ dotenvx get
{"HELLO":"World"}
get --format shell
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
get --format eval
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.
get --all
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"}
get --all --pretty-print
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 KEY value
Set an encrypted key/value (on by default).
$ touch .env
$ dotenvx set HELLO World
set HELLO with encryption (.env)
set KEY value -f
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 KEY value -fk
Specify path to .env.keys
. This is useful with monorepos.
$ mkdir -p apps/app1
$ touch apps/app1/.env
$ dotenvx set HELLO world -fk .env.keys -f apps/app1/.env
set HELLO with encryption (.env)
Put it to use.
$ dotenvx get -fk .env.keys -f apps/app1/.env
Use it with a relative path.
$ cd apps/app1
$ dotenvx get -fk ../../.env.keys -f .env
set KEY "value with spaces"
Set a value containing spaces.
$ touch .env.ci
$ dotenvx set HELLO "my ci" -f .env.ci
set HELLO with encryption (.env.ci)
set KEY -- "- + * รท"
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 KEY value --plain
Set a plaintext key/value.
$ touch .env
$ dotenvx set HELLO World --plain
set HELLO (.env)
encrypt
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 -f
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
encrypt -fk
Specify path to .env.keys
. This is useful with monorepos.
$ mkdir -p apps/app1
$ echo "HELLO=World" > apps/app1/.env
$ dotenvx encrypt -fk .env.keys -f apps/app1/.env
โ encrypted (apps/app1/.env)
Put it to use.
$ dotenvx run -fk .env.keys -f apps/app1/.env
Use with a relative path.
$ cd apps/app1
$ dotenvx run -fk ../../.env.keys -f .env
encrypt -k
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)
encrypt -ek
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 --stdout
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
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 -f
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 -fk
Specify path to .env.keys
. This is useful with monorepos.
$ mkdir -p apps/app1
$ echo "HELLO=World" > apps/app1/.env
$ dotenvx encrypt -fk .env.keys -f apps/app1/.env
โ encrypted (apps/app1/.env)
$ dotenvx decrypt -fk .env.keys -f apps/app1/.env
โ decrypted (apps/app1/.env)
decrypt -k
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 -ek
Decrypt the contents inside an encrypted .env
file except for an excluded 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 --stdout
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
keypair
Print public/private keys for .env
file.
$ echo "HELLO=World" > .env
$ dotenvx encrypt
$ dotenvx keypair
{"DOTENV_PUBLIC_KEY":"","DOTENV_PRIVATE_KEY":""}
keypair -f
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":"","DOTENV_PRIVATE_KEY_PRODUCTION":""}
keypair -fk
Specify path to .env.keys
. This is useful for printing public/private keys for monorepos.
$ mkdir -p apps/app1
$ echo "HELLO=World" > apps/app1/.env
$ dotenvx encrypt -fk .env.keys -f apps/app1/.env
$ dotenvx keypair -fk .env.keys -f apps/app1/.env
{"DOTENV_PUBLIC_KEY":"","DOTENV_PRIVATE_KEY":""}
keypair DOTENV_PRIVATE_KEY
Print specific keypair for .env
file.
$ echo "HELLO=World" > .env
$ dotenvx encrypt
$ dotenvx keypair DOTENV_PRIVATE_KEY
keypair --format shell
Print a shell formatted response of public/private keys.
$ echo "HELLO=World" > .env
$ dotenx encrypt
$ dotenvx keypair --format shell
DOTENV_PUBLIC_KEY= DOTENV_PRIVATE_KEY=
ls
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
ls directory
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
ls -f
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
ls -ef
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
rotate
Rotate public/private keys for .env
file and re-encrypt all encrypted values.
$ echo "HELLO=World" > .env
$ dotenvx encrypt
โ encrypted (.env)
$ dotenvx rotate
โ rotated (.env)
rotate -f
Rotate public/private keys for a specified encrypted .env
file and re-encrypt all encrypted values.
$ echo "HELLO=World" > .env
$ echo "HELLO=Production" > .env.production
$ dotenvx encrypt -f .env.production
โ encrypted (.env.production)
$ dotenvx rotate -f .env.production
โ rotated (.env.production)
rotate -fk
Specify path to .env.keys
. This is useful with monorepos.
$ mkdir -p apps/app1
$ echo "HELLO=World" > apps/app1/.env
$ dotenvx encrypt -fk .env.keys -f apps/app1/.env
โ encrypted (apps/app1/.env)
$ dotenvx rotate -fk .env.keys -f apps/app1/.env
โ rotated (apps/app1/.env)
rotate -k
Rotate the contents of a specified key inside an encrypted .env
file.
$ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
โ encrypted (.env)
$ dotenvx rotate -k HELLO
โ rotated (.env)
Even specify a glob pattern.
$ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
โ encrypted (.env)
$ dotenvx rotate -k "HE*"
โ rotated (.env)
rotate -ek
Rotate the encrypted contents inside an encrypted .env
file except for an excluded key.
$ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
โ encrypted (.env)
$ dotenvx rotate -ek HOLA
โ rotated (.env)
Even specify a glob pattern.
$ echo "HELLO=World\nHOLA=Mundo" > .env
$ dotenvx encrypt
โ encrypted (.env)
$ dotenvx rotate -ek "HO*"
โ rotated (.env)
rotate --stdout
Rotate the contents of an encrypted .env
file and send to stdout.
$ dotenvx rotate --stdout
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY="034af93e93708b994c10f236c96ef88e47291066946cce2e8d98c9e02c741ced45"
# .env
HELLO="encrypted:12345"
or send to a file:
$ dotenvx rotate --stdout > somefile.txt
help
Output help for dotenvx
.
$ dotenvx help
Usage: dotenvx run -- yourcommand
a secure dotenvโfrom the creator of `dotenv`
Options:
-l, --log-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 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 environment variable(s) set as string (example: "HELLO=World") (default: [])
-f, --env-file path(s) to your env file(s) (default: [])
-fv, --env-vault-file path(s) to your .env.vault file(s) (default: [])
-o, --overload override existing env variables
--convention 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@1.X.X] injecting env (1) from .env
Hello World
--version
Check current version of dotenvx
.
$ dotenvx --version
X.X.X
CLI extensions.
ext genexample
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=""
ext genexample -f
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=""
ext genexample directory
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=""
ext gitignore
Gitignore your .env
files.
$ dotenvx ext gitignore
โ ignored .env* (.gitignore)
ext gitignore --pattern
Gitignore specific pattern(s) of .env
files.
$ dotenvx ext gitignore --pattern .env.keys
โ ignored .env.keys (.gitignore)
ext precommit
Prevent .env
files from being committed to code.
$ dotenvx ext precommit
[dotenvx][precommit] .env files (1) protected (encrypted or gitignored)
ext precommit --install
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]
ext precommit directory
Prevent .env
files from being committed to code inside a specified path to a directory.
$ echo "HELLO=World" > .env
$ mkdir -p apps/backend
$ echo "HELLO=Backend" > apps/backend/.env
$ dotenvx ext precommit apps/backend
[dotenvx][precommit] apps/backend/.env not protected (encrypted or gitignored)
ext prebuild
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"]
ext prebuild directory
Prevent .env
files from being built into your docker containers inside a specified path to a directory.
Add it to your Dockerfile
.
# Dockerfile
RUN curl -fsS https://dotenvx.sh | sh
...
RUN dotenvx ext prebuild apps/backend
CMD ["dotenvx", "run", "--", "node", "apps/backend/index.js"]
ext scan
Scan for leaked secrets.
$ dotenvx ext scan
100 commits scanned.
no leaks found
Uses gitleaks under the hood.
Use dotenvx directly in code.
config()
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.X.X] injecting env (1) from .env
Hello World
It defaults to looking for a .env
file.
config(path: ['.env.local', '.env'])
- multiple files
Specify path(s) to multiple .env files.
# .env.local
HELLO="Me"
# .env
HELLO="World"
// index.js
require('@dotenvx/dotenvx').config({path: ['.env.local', '.env']})
// esm
// import dotenvx from "@dotenvx/dotenvx";
// dotenvx.config({path: ['.env.local', '.env']});
console.log(`Hello ${process.env.HELLO}`)
$ node index.js
[dotenvx@1.X.X] injecting env (1) from .env.local, .env
Hello Me
config(overload: true)
- overload
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})
// esm
// import dotenvx from "@dotenvx/dotenvx";
// dotenvx.config({path: ['.env.local', '.env'], overload: true});
console.log(`Hello ${process.env.HELLO}`)
$ node index.js
[dotenvx@1.X.X] injecting env (1) from .env.local, .env
Hello World
config(quiet: true)
- quiet
Suppress all output (except errors).
# .env
HELLO="World"
// index.js
require('@dotenvx/dotenvx').config({path: ['.env.missing', '.env'], quiet: true})
// esm
// import dotenvx from "@dotenvx/dotenvx";
// dotenvx.config({path: ['.env.missing', '.env'], quiet: true});
console.log(`Hello ${process.env.HELLO}`)
$ node index.js
Error: [MISSING_ENV_FILE] missing .env.missing file (/path/to/.env.missing)
Hello World
config(strict: true)
- strict
Exit with code 1
if any errors are encountered - like a missing .env file or decryption failure.
# .env
HELLO="World"
// index.js
require('@dotenvx/dotenvx').config({path: ['.env.missing', '.env'], strict: true})
// esm
// import dotenvx from "@dotenvx/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)
config(ignore:)
- ignore
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']})
// esm
// import dotenvx from "@dotenvx/dotenvx";
// dotenvx.config({path: ['.env.missing', '.env'], ignore: ['MISSING_ENV_FILE']});
console.log(`Hello ${process.env.HELLO}`)
$ node index.js
[dotenvx@1.X.X] injecting env (1) from .env
Hello World
config(envKeysFile:)
- envKeysFile
Use envKeysFile
to customize the path to your .env.keys
file. This is useful with monorepos.
# .env
HELLO="World"
// index.js
require('@dotenvx/dotenvx').config({path: ['.env'], envKeysFile: '../../.env.keys'})
parse(src)
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
parse(src, {processEnv:})
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
parse(src, {privateKey:})
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
set(KEY, value)
Programmatically set an environment variable.
// index.js
const dotenvx = require('@dotenvx/dotenvx')
dotenvx.set('HELLO', 'World', { path: '.env' })
get(KEY)
- Decryption at Access
Programmatically get an environment variable at access/runtime.
// index.js
const dotenvx = require('@dotenvx/dotenvx')
const decryptedValue = dotenvx.get('HELLO')
console.log(decryptedValue)
This is known as Decryption at Access and is written about in the whitepaper.
ย
> Dotenvx Radar is a commercial extension for dotenvx.
Observe, version, and back up your environment variables at runtime.
$ curl -sfS https://dotenvx.sh/radar | sh
$ dotenvx-radar login
โ logged in [username]
$ dotenvx run -- yourcommand
[dotenvx@1.0.0] ๐ก radar active
[dotenvx@1.0.0] injecting env (1) from .env
That's it! Your environment variables are auto-observed and backed up by Radar.
login
Log in to radar.
$ dotenvx-radar login
press Enter to open [https://radar.dotenvx.com/login/device] and enter code [D9C1-03BC]... (Y/n)
โ น waiting on browser authorization
โ logged in [username] to this device and activated token [dxo_6kjPifIโฆ]
logout
Log out of radar.
$ dotenvx-radar logout
โ logged out [username] from this device and revoked token [dxo_5ZrwRXVโฆ]
status
Check current status of radar - on
or off
(logged in or out).
$ dotenvx-radar status
on
settings
Check and configure various settings for radar - username
, token
, and more.
$ dotenvx-radar settings
Usage: dotenvx-radar settings [options] [command]
โ๏ธ settings
Options:
-h, --help display help for command
Commands:
username print your username
token [options] print your access token (--unmask)
hostname print hostname
help [command] display help for command
ย
> Dotenvx: Reducing Secrets Risk with Cryptographic Separation > > Abstract. An ideal secrets solution would not only centralize secrets but also contain the fallout of a breach. While secrets managers offer centralized storage and distribution, their design creates a large blast radius, risking exposure of thousands or even millions of secrets. We propose a solution that reduces the blast radius by splitting secrets management into two distinct components: an encrypted secrets file and a separate decryption key. > > ... > > Read the whitepaper
ย
> Go deeper with dotenvx
โ detailed framework and platform guides.
>
ย
Dotenvx uses Elliptic Curve Integrated Encryption Scheme (ECIES) to encrypt each secret with a unique ephemeral key, while ensuring it can be decrypted using a long-term private key.
When you initialize encryption, a DOTENV_PUBLIC_KEY (encryption key) and DOTENV_PRIVATE_KEY (decryption key) are generated. The DOTENV_PUBLIC_KEY is used to encrypt secrets, and the DOTENV_PRIVATE_KEY is securely stored in your cloud secrets manager or .env.keys file.
Your encrypted .env file is then safely committed to code. Even if the file is exposed, secrets remain protected since decryption requires the separate DOTENV_PRIVATE_KEY, which is never stored alongside it. Read the whitepaper for more details.
Yes. Dotenvx encrypts secrets using AES-256 with ephemeral keys, ensuring that even if the encrypted .env file is exposed, its contents remain secure. The encryption keys themselves are protected using Secp256k1 elliptic curve cryptography, which is widely used for secure key exchange in technologies like Bitcoin.
This means that every secret in the .env file is encrypted with a unique AES-256 key, and that key is further encrypted using a public key (Secp256k1). Even if an attacker obtains the encrypted .env file, they would still need the corresponding private keyโstored separately in a secrets managerโto decrypt anything.
Breaking this encryption would require brute-forcing both AES-256 and elliptic curve cryptography, which is computationally infeasible with current technology. Read the whitepaper for more details.
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: