@dotenvx/dotenvx
v2.11.1
Published
a secure dotenv–from the creator of `dotenv`
Readme
a secure dotenv–from the creator of dotenv.
- run anywhere (cross-platform)
- multi-environment
- encrypted envs
Quickstart

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!
npm i -g @dotenvx/dotenvx
dotenvx encrypt
curl -sfS https://dotenvx.sh | sh
dotenvx encrypt
brew tap dotenvx/brew
brew trust dotenvx/brew
brew install dotenvx
dotenvx encrypt
docker run -it --rm -v $(pwd):/app dotenv/dotenvx encrypt
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 encrypt
winget install dotenvx
dotenvx encrypt
Run Anywhere
$ 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
> :-DMore examples
Run Claude with your real secrets while redacting them from its output.
$ echo "SECRET=super-secret-value" > .env
$ dotenvx run --redact --quiet -- claude -p 'Print the value of $SECRET'
[REDACTED]Run Codex with your real secrets while redacting them from its output.
$ echo "SECRET=super-secret-value" > .env
$ dotenvx run --redact --quiet -- codex exec 'Print the value of $SECRET'
[REDACTED]// 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 WorldPreface Astro scripts with dotenvx run -- and read your env values in Astro.
{
"scripts": {
"dev": "dotenvx run -- astro dev",
"build": "dotenvx run -- astro build",
"preview": "dotenvx run -- astro preview"
}
}export async function GET() {
return new Response(
JSON.stringify({
HELLO: process.env.HELLO,
}),
{
status: 200,
headers: {
"Content-Type": "application/json",
},
}
);
}see astro guide
Preface Expo scripts with dotenvx run --.
{
"scripts": {
"start": "dotenvx run -- expo start",
"reset-project": "node ./scripts/reset-project.js",
"android": "dotenvx run -- expo start --android",
"ios": "dotenvx run -- expo start --ios",
"web": "dotenvx run -- expo start --web",
"lint": "expo lint"
}
}see expo guide
Install Dotenvx and @dotenvx/next-env.
$ npm install @dotenvx/dotenvx
$ npm install @dotenvx/next-envOverride @next/env in your package.json.
{
"overrides": {
"@next/env": "npm:@dotenvx/next-env"
}
}Encrypt your .env file.
$ npx dotenvx encrypt
◈ encrypted (.env)Your encrypted secrets are automatically injected and readable in Next.js.
import { NextResponse } from 'next/server'
export async function GET() {
return NextResponse.json({
HELLO: process.env.HELLO
})
}Set DOTENV_PRIVATE_KEY in production before deploying.
$ dotenvx encrypt -f .env.txt// src/index.js
import envSrc from '../.env.txt'
import dotenvx from '@dotenvx/dotenvx'
const config = dotenvx.config({ envs: [{ type: 'env', value: envSrc, privateKeyName: 'DOTENV_PRIVATE_KEY' }] })
const envx = config.parsed
export default {
async fetch(request, env, ctx) {
return new Response(`Hello ${envx.HELLO}`)
}
}"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev --var $(dotenvx keypair -f .env.txt --format=colon)",
"start": "wrangler dev --var $(dotenvx keypair -f .env.txt --format=colon)",
}$ 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 "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 cipherInstead, use
dotenvxas designed, by installing the cli as a binary - via curl, brew, etc.
$ 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 servesee framework guides
$ docker run -it --rm -v $(pwd):/app dotenv/dotenvx run -- node index.jsOr 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 ["/usr/local/bin/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 | sh
# vercel
npm install @dotenvx/dotenvx --savesee 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
[[email protected]] injecting env (1) from .env.production
Hello World# use dotenvx with asdf
$ asdf plugin add dotenvx
$ asdf install dotenvx latestthank you @jgburet of Paris 🇫🇷
# use as a git submodule
$ git dotenvx run -- node index.js
$ git dotenvx run -- next dev
$ git dotenvx run -- npm startReference 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
[[email protected]] injecting env (2) from .env
DATABASE_URL postgres://username@localhost/my_databaseAdd 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
[[email protected]] injecting env (1) from .env
DATABASE_URL postgres://yourusername@localhost/my_database
Multiple Environments
Create a
.env.productionfile and use-fto 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
[[email protected]] injecting 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
[[email protected]] injecting env (1) from .env.local,.env
Hello localNote 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.
$ echo "HELLO=local" > .env.local
$ echo "HELLO=World" > .env
$ dotenvx run -f .env.local -f .env --overload -- node index.js
[[email protected]] injecting env (1) from .env.local,.env
Hello WorldNote that with --overload subsequent files DO override pre-existing variables defined in previous files.
$ 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
[[email protected]] injecting 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
[[email protected]] injecting env (1) from .env.production
Hello productionUse --quiet to suppress all output (except errors).
$ echo "HELLO=production" > .env.production
$ dotenvx run -f .env.production --quiet -- node index.js
Hello productionYou can also set DOTENV_CONFIG_QUIET=true.
$ DOTENV_CONFIG_QUIET=true dotenvx run -f .env.production -- node index.js
Hello productionSet --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 productionAvailable log levels are error, warn, info, verbose, debug, silly
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)
Encryption
Add encryption to your
.envfiles 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
[[email protected]] 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
[[email protected]] injecting env (2) from .env.production
Hello ProductionNote 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
[[email protected]] injecting env (2) from .env.ci
Hello CiNote 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
[[email protected]] injecting env (3) from .env, .env.production
Hello WorldNote 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?
Point -f at a directory to load the .env inside it. From a workspace, this makes a shared root .env available without repeating its filename.
my-monorepo/
.env
.env.keys
apps/
web/
index.js$ cd apps/web
$ dotenvx get HELLO -f ../..
World
$ dotenvx run -f ../.. -- node index.js
[[email protected]] injecting env (1) from ../../.env
Hello WorldEncrypted values work without extra configuration when .env.keys sits beside the resolved .env.
The directory also becomes the base when using a convention:
$ dotenvx run -f ../.. --convention=nextjs -- node index.js
[[email protected]] injecting env (1) from ../../.env.development.local, ../../.env.local, ../../.env.development, ../../.env
Hello development localIf a workspace has its own .env but shares the root .env.keys, point -fk at the root directory:
$ dotenvx run -f . -fk ../.. -- node index.js$ echo "HELLO=World" > .env
$ dotenvx encrypt --stdout
$ dotenvx encrypt --stdout > .env.encrypted
secp256k1is 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 [email protected].
After encryption, DOTENV_PUBLIC_KEY lives in your encrypted .env file. This means agents and automation can keep running dotenvx set and dotenvx encrypt without reading .env.keys.
$ chmod a-r .env.keys
$ dotenvx set HELLO World
◈ encrypted HELLO (.env)
$ dotenvx encrypt
◈ encrypted (.env)Keep .env.keys unreadable by agents, while still letting them safely update encrypted values.
Advanced
Become a
dotenvxpower user.
CLI 📟
Advanced CLI commands.
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
[[email protected]] injecting env (2) from .env
DATABASE_URL postgres://username@localhost/my_databaseUse 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
[[email protected]] injecting env (3) from .env
DATABASE_HOST localhost
DATABASE_PORT 5432
API_URL https://api.example.comUse 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
[[email protected]] injecting env (4) from .env
NODE_ENV production
DEBUG_MODE false
LOG_LEVEL error
CACHE_ENABLED trueComplete 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- Default syntax (
-): Use variable value or fallback - Alternate syntax (
+): Use alternate value or empty string
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
[[email protected]] injecting env (1) from .env
DATABASE_URL postgres://yourusername@localhost/my_databasePrevent 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 WorldDotenvx 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-----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 file (/Users/scottmotte/Code/dotenvx/playground/apr-16/.env.missing). fix: [echo "HELLO=World" > .env.missing]or when missing a KEY:
$ echo "HELLO=World" > .env
$ dotenvx get GOODBYE
[MISSING_KEY] missing key (GOODBYE)Run a command using the .env file in a directory. This is useful with monorepos.
$ dotenvx run -f ../.. -- node index.js
[[email protected]] injecting env (1) from ../../.env
Hello WorldCompose 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
[[email protected]] injecting env (1) from .env.local, .env
Hello localNote 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.
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
[[email protected]] injecting env (1) from .env, and --env flag
Hello StringRun any command with real environment variables while automatically redacting their values from stdout and stderr. Keys ending in _PLAIN are left visible.
$ echo "SECRET=super-secret-value" > .env
$ echo "VISIBLE_PLAIN=visible-value" >> .env
$ echo "console.log(process.env.SECRET, process.env.VISIBLE_PLAIN)" > index.js
$ dotenvx run --redact --quiet -- node index.js
[REDACTED] visible-valueRedaction is off by default. It applies to every key declared in .env files and --env flags unless the key ends in _PLAIN. If an existing environment variable takes precedence, its effective value is redacted too. Matching is exact, so transformed or derived values are not redacted.
When stdin, stdout, and stderr are attached to a terminal, dotenvx preserves interactive behavior on macOS and Linux systems with script available. Piped and redirected commands continue to use normal stdin, stdout, and stderr streams.
Run Claude in print mode with real environment variables while redacting any values it prints.
$ echo "SECRET=super-secret-value" > .env
$ dotenvx run --redact --quiet -- claude -p 'Print the value of $SECRET'
[REDACTED]Start a fully interactive Claude session. Claude receives the real values, but any values it prints are redacted.
$ echo "SECRET=super-secret-value" > .env
$ dotenvx run --redact --quiet -- claudeRun Codex non-interactively with real environment variables while redacting any values it prints.
$ echo "SECRET=super-secret-value" > .env
$ dotenvx run --redact --quiet -- codex exec 'Print the value of $SECRET'
[REDACTED]Start a fully interactive Codex session. Codex receives the real values, but any values it prints are redacted.
$ echo "SECRET=super-secret-value" > .env
$ dotenvx run --redact --quiet -- codexOverride 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
[[email protected]] injecting env (1) from .env.local, .env
Hello WorldNote that with --overload subsequent files DO override pre-existing variables defined in previous files.
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/v1For 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.
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
[[email protected]] injecting env (2) from .env
Hello encryptedDecrypt 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
[[email protected]] injecting env (2) from .env.production
Hello production encryptedNote 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
[[email protected]] injecting env (2) from .env.ci
Hello ci encryptedNote 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
[[email protected]] 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
[[email protected]] injecting env (3) from .env.production, .env
Hello production encryptedCompose 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
[[email protected]] injecting env (1) from .env.production
Hello productionSet 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
[[email protected]] injecting env (1) from .env.production
executing process command [node index.js]
expanding process command to [/opt/homebrew/bin/node index.js]
Hello productionUse --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 productionYou can also set DOTENV_CONFIG_QUIET=true.
$ DOTENV_CONFIG_QUIET=true dotenvx run -f .env.production -- node index.js
Hello productionSet --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 productionAvailable 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 file (/path/to/.env.missing). fix: [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
[[email protected]] injecting env (1) from .env.development.local, .env.local, .env.development, .env
Hello development localYou can also set DOTENV_CONFIG_CONVENTION=nextjs.
$ DOTENV_CONFIG_CONVENTION=nextjs dotenvx run -- node index.js
[[email protected]] injecting env (1) from .env.development.local, .env.local, .env.development, .env
Hello development local(more conventions available upon request)
Run a command using Next.js' convention from a directory. This is useful with monorepos.
$ dotenvx run -f ../.. --convention=nextjs -- node index.js
[[email protected]] injecting env (1) from ../../.env.development.local, ../../.env.local, ../../.env.development, ../../.env
Hello development localLoad 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
[[email protected]] injecting env (1) from .env.development.local, .env.development, .env.local, .env
Hello development localYou can also set DOTENV_CONFIG_CONVENTION=flow.
$ NODE_ENV=development DOTENV_CONFIG_CONVENTION=flow dotenvx run -- node index.js
[[email protected]] injecting env (1) from .env.development.local, .env.development, .env.local, .env
Hello development localFurther, 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
[[email protected]] injecting env (1) from .env.development.local, .env.development, .env.local, .env
Hello development localSpecify 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 -- yourcommandInject masked values into the command. By default, up to the first six characters are visible.
$ echo "SECRET=abcdefghijkl" > .env
$ echo "console.log(process.env.SECRET)" > index.js
$ dotenvx run --mask --quiet -- node index.js
abcdef******Pass a number to control how many characters are visible, such as --mask 0 to fully mask values.
Set Armor ⛨ token for retrieving armored private keys.
$ dotenvx run --token "$DOTENVX_ARMOR_TOKEN" -- yourcommandTurn off OS secret store lookups.
$ dotenvx run --no-native -- yourcommandTurn off Dotenvx Armor ⛨ features.
$ dotenvx run --no-armor -- yourcommandReturn a single environment variable's value.
$ echo "HELLO=World" > .env
$ dotenvx get HELLO
WorldReturn a masked environment variable value. By default, up to the first six characters are visible.
$ echo "SECRET=abcdefghijkl" > .env
$ dotenvx get SECRET --mask
abcdef******Pass a number to control how many characters are visible, such as --mask 0 to fully mask values.
Turn off OS secret store lookups for get.
$ dotenvx get HELLO --no-nativeReturn 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
productionReturn a single environment variable's value from the .env file in a directory. This is useful with monorepos.
$ dotenvx get HELLO -f ../..
WorldSpecify 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
worldReturn a single environment variable's value from a --env string.
$ dotenvx get HELLO --env HELLO=String -f .env.production
StringReturn 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
WorldExit 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 key (DOES_NOT_EXIST)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 localYou can also set DOTENV_CONFIG_CONVENTION=nextjs.
$ DOTENV_CONFIG_CONVENTION=nextjs dotenvx get HELLO
development localReturn a single environment variable's value using Next.js' convention from a directory. This is useful with monorepos.
$ dotenvx get HELLO -f ../.. --convention=nextjs
development localReturn 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 localYou can also set DOTENV_CONFIG_CONVENTION=flow.
$ NODE_ENV=development DOTENV_CONFIG_CONVENTION=flow dotenvx get HELLO
development localFurther, we recommend using DOTENV_ENV over NODE_ENV– as dotenvx works everywhere, not just node.
$ DOTENV_ENV=development dotenvx get HELLO --convention=flow
development localReturn 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=valueThis 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 Worldor with export.
$ echo "console.log('Hello ' + process.env.KEY + ' ' + process.env.HELLO)" > index.js
$ export $(dotenvx get --format=shell)
$ node index.js
Hello value WorldReturn 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 WorldBe 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)Works with unreadable .env.keys when .env already contains DOTENV_PUBLIC_KEY.
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)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/.envUse it with a relative path.
$ cd apps/app1
$ dotenvx get -fk ../../.env.keys -f .envSet 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)Set a plaintext key/value inside an encrypted .env file by ending the key with _PLAIN.
$ touch .env
$ dotenvx set HELLO_PLAIN World
set HELLO_PLAIN (.env)Keys ending in _PLAIN are not encrypted by dotenvx set or dotenvx encrypt.
Turn off OS secret store lookups for set.
$ dotenvx set HELLO World --no-nativeEncrypt the contents of a .env file to an encrypted .env file.
$ echo "HELLO=World" > .env
$ dotenvx encrypt
◈ encrypted (.env) + local key (.env.keys)
⮕ next run [dotenvx gitignore --pattern .env.keys] to gitignore .env.keys
⮕ next run [DOTENV_PRIVATE_KEY='122...0b8' dotenvx run -- yourcommand] to test decryption locallyWorks with unreadable .env.keys when .env already contains DOTENV_PUBLIC_KEY.
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) + local key (.env.keys)
⮕ next run [dotenvx gitignore --pattern .env.keys] to gitignore .env.keys
⮕ next run [DOTENV_PRIVATE_KEY='bff...bc4' dotenvx run -- yourcommand] to test decryption locallyTurn off OS secret store lookups for encrypt.
$ dotenvx encrypt --no-native
◈ encrypted (.env)Turn off Dotenvx Armor ⛨ features for encrypt.
$ dotenvx encrypt --no-armor
◈ encrypted (.env)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/.envUse with a relative path.
$ cd apps/app1
$ dotenvx run -fk ../../.env.keys -f .envSpecify 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)Skip encryption for keys ending in _PLAIN.
$ echo "HELLO=World\nHELLO_PLAIN=visible" > .env
$ dotenvx encrypt
◈ encrypted (.env)HELLO is encrypted. HELLO_PLAIN stays plaintext.
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.txtDecrypt the contents of an encrypted .env file to an unencrypted .env file.
$ echo "HELLO=World" > .env
$ dotenvx encrypt
◈ encrypted (.env)
$ dotenvx decrypt
◇ decrypted (.env)Turn off OS secret store lookups for decrypt.
$ dotenvx decrypt --no-native
◇ 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)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 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*"
◇ decrypted (.env)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*"
◇ decrypted (.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.txtDecrypt the contents of an encrypted .env file to stdout with its values masked. The encrypted .env file remains unchanged.
$ echo "SECRET=abcdefghijkl" > .env
$ dotenvx encrypt
◈ encrypted (.env)
$ dotenvx decrypt --stdout --mask
...
SECRET="abcdef******"Pass a number to control how many characters are visible, such as --mask 0 to fully mask values.
Print public/private keys for .env file.
$ echo "HELLO=World" > .env
$ dotenvx encrypt
$ dotenvx keypair
{"DOTENV_PUBLIC_KEY":"<publicKey>","DOTENV_PRIVATE_KEY":"<privateKey>"}Turn off OS secret store lookups for keypair.
$ dotenvx keypair --no-native
{"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>"}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":"<publicKey>","DOTENV_PRIVATE_KEY":"<privateKey>"}Print specific keypair for .env file.
$ echo "HELLO=World" > .env
$ dotenvx encrypt
$ dotenvx keypair DOTENV_PRIVATE_KEY
<privateKey>Print a shell formatted response 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
└─ .envPrint 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
└─ .envGlob .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.prodGlob .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
└─ .envPrint all matching .env files as a JSON array of absolute filepaths. Progress and summary details are written to stderr, so stdout can be safely piped to another command or file.
$ dotenvx ls --json
[
"/path/to/project/.env",
"/path/to/project/apps/backend/.env"
]
$ dotenvx ls --json > dotenv-files.jsonIn one command, generate a .env.example file from your current .env file contents.
$ echo "HELLO=World" > .env
$ dotenvx genexample
▣ generated (.env.example)# .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 genexample -f .env -f .env.production
▣ generated (.env.example)# .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 genexample apps/backend
▣ generated (.env.example)# apps/backend/.env.example
HELLO=""Gitignore your .env files.
$ dotenvx gitignore
▣ ignored .env* (.gitignore)Gitignore specific pattern(s) of .env files.
$ dotenvx gitignore --pattern .env.keys
▣ ignored .env.keys (.gitignore)Prevent .env files from being committed to code.
$ 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 precommit --install
▣ dotenvx precommit installed [.git/hooks/pre-commit]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 precommit apps/backend
▣ apps/backend/.env not protected (encrypted or gitignored)Prevent .env files from being built into your docker containers.
Add it to your Dockerfile.
# Install via script
RUN curl -fsS https://dotenvx.sh | sh
# Or copy binary from official image
COPY --from=dotenv/dotenvx:latest /usr/local/bin/dotenvx /bin/local/bin
# ... orther container commands
RUN dotenvx prebuild
CMD ["/usr/local/bin/dotenvx", "run", "--", "node", "index.js"]Prevent .env files from being built into your docker containers inside a specified path to a directory.
Add it to your Dockerfile.
# Install via script
RUN curl -fsS https://dotenvx.sh | sh
# Or copy binary from official image
COPY --from=dotenv/dotenvx:latest /usr/local/bin/dotenvx /bin/local/bin
# ... orther container commands
RUN dotenvx prebuild apps/backend
CMD ["/usr/local/bin/dotenvx", "run", "--", "node", "apps/backend/index.js"]Lock private keys with a local passphrase to keep them protected inside .env.keys.
# example
DOTENV_PRIVATE_KEY=locked:02f5b97ad58b49ae324cd4e7937bc19b251d006b31cacf46f789eeaf03f923cedc:AZIPDxKqjPLiGl5b4CqVGbR3CIBDUcqHthGaoeWLoUvxbTHJkj3jGoGWGaxFSDUJGQUmWDaExRzxKpVydYF_7qiWr1ecqksOFho5t3EMwKbqX2-y-LZO9K3a4SJaYAjDJXpn3NwG4vAt1oLmGALock a private key in .env.keys with a local passphrase.
$ dotenvx lock upHere's what a locked key looks like:
# .env.keys
DOTENV_PRIVATE_KEY=locked:02f5b97ad58b49ae324cd4e7937bc19b251d006b31cacf46f789eeaf03f923cedc:AZIPDxKqjPLiGl5b4CqVGbR3CIBDUcqHthGaoeWLoUvxbTHJkj3jGoGWGaxFSDUJGQUmWDaExRzxKpVydYF_7qiWr1ecqksOFho5t3EMwKbqX2-y-LZO9K3a4SJaYAjDJXpn3NwG4vAt1oLmGASpecify files with -f and -fk.
$ dotenvx lock up -f .env.pr
