a secure dotenvβfrom the creator of `dotenv`
100K+
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!
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
Β
$ 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
Run Claude with your real secrets while redacting them from its output.
Prerequisite: install Claude Codeβ to get the claude command.
$ curl -fsSL https://claude.ai/install.sh | bash
$ claude --version
$ echo "HELLO=World" > .env
$ dotenvx run --redact -- claude -p 'Run `dotenvx get HELLO` and echo back just Hello VALUE' --dangerously-skip-permissions
Hello [REDACTED]
Run Codex with your real secrets while redacting them from its output.
Prerequisite: install the Codex CLIβ to get the codex command.
$ npm install -g @openai/codex
$ codex --version
$ echo "HELLO=World" > .env
$ dotenvx run --redact -- codex exec 'Run `dotenvx get HELLO` and echo back just Hello VALUE' --skip-git-repo-check
Hello [REDACTED]
Run Cursor with your real secrets while redacting them from its output.
Prerequisite: install the separate Cursor CLIβ . Installing the Cursor desktop app does not necessarily install the agent command.
$ curl https://cursor.com/install -fsS | bash
$ agent --version
$ echo "HELLO=World" > .env
$ dotenvx run --redact -- agent -p --force 'Run `dotenvx get HELLO` and echo back just Hello VALUE' --output-format text
Hello [REDACTED]
Run with secrets resolved directly from 1Password.
$ echo "HELLO=op://Personal/hello/password" > .env
$ dotenvx run -- sh -c 'echo Hello $HELLO'
Hello World
Run with secrets resolved directly from Bitwarden Password Manager.
$ echo 'HELLO="bw://My Hello Login/password"' > .env
$ dotenvx run -- sh -c 'echo Hello $HELLO'
Hello World
Install the Bitwarden Password Manager CLIβ before running dotenvx.
// 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
Preface 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-env
Override @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 serve
$ 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 ["/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 --save
// 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 latest
thank you @jgburetβ of Paris π«π·
# use as a git submodule
$ git dotenvx run -- node index.js
$ git dotenvx run -- next dev
$ git dotenvx run -- npm start
Reference and expand variables already on your machine for use in your .env file.
# .env
USERNAME="username"
DATABASE_URL="postgres://${USERNAME}@localhost/my_database"
// index.js
console.log('DATABASE_URL', process.env.DATABASE_URL)
$ dotenvx run --debug -- node index.js
[[email protected]] injecting env (2) from .env
DATABASE_URL postgres://username@localhost/my_database
Add the output of a command to one of your variables in your .env file.
# .env
DATABASE_URL="postgres://$(whoami)@localhost/my_database"
// index.js
console.log('DATABASE_URL', process.env.DATABASE_URL)
$ dotenvx run --debug -- node index.js
[[email protected]] injecting env (1) from .env
DATABASE_URL postgres://yourusername@localhost/my_database
Β
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,.env -- node index.js
[[email protected]] injecting env (1) from .env.local,.env
Hello local
Comma-separate multiple files after a single -f. 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,.env --overload -- node index.js
[[email protected]] injecting env (1) from .env.local,.env
Hello World
Note 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 production
Use --quiet to suppress all output (except errors).
$ echo "HELLO=production" > .env.production
$ dotenvx run -f .env.production --quiet -- node index.js
Hello production
You can also set DOTENV_QUIET=true.
$ DOTENV_QUIET=true dotenvx run -f .env.production -- node index.js
Hello production
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
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
.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β .
New private keys created by encrypt and encrypted set default to your OS secret store: macOS Keychain, Windows Credential Manager, or Linux Secret Service. The interactive picker offers Local Custody through your OS secret store, Local Custody through 1Password when available, and Managed Custody through Armor when logged in. When only the OS store is available, it is selected automatically. Existing .env.keys files are not automatically migrated.
Linux requires secret-tool (typically the libsecret-tools package), a running Secret Service such as GNOME Keyring, and a user D-Bus session. When native tooling or the service is missing, dotenvx reports the fallback to .env.keys. Locked, denied, timed-out, or unverified storage operations fail without falling back to a plaintext key file.
CI uses file storage. To explicitly use file storage elsewhere, run dotenvx encrypt --no-native --no-armor --no-1password (or pass all three flags to set). For deployment, continue supplying the private key through your platform's secret injection.
The 1Password option appears when CLI version 2 is installed and an account is configured (or OP_SERVICE_ACCOUNT_TOKEN is set). Detection does not sign in or unlock your vault. Selecting it authenticates as needed, uses the default vault selected by the 1Password CLI, and verifies the saved key before writing the encrypted env file. An unavailable, denied, or cancelled 1Password operation fails without falling back to local key storage.
1Password custody stores the key in your default vault and saves only its account/item reference in dotenvx's per-user settings. run, get, and keypair then retrieve it automatically, subject to 1Password authorization. --no-1password (where supported) or DOTENVX_NO_1PASSWORD=true disables that integration. The local reference is needed for automatic lookup on this machine; on another machine, the key is still accessible in the vault but its locator must also be configured for automatic lookup. No plaintext private key is written to .env.keys or passed in command arguments. The βLocal Custodyβ label refers to using your own password manager; 1Password vaults may sync off-machine.
To move an existing key into the OS store, run dotenvx native up. It verifies the stored key before removing it from .env.keys. Use dotenvx native push to keep a file copy, dotenvx native pull to export a copy, or dotenvx native down to move it back to .env.keys. Add -f .env.production for a particular env file. Keep a recoverable copy in your team's secret manager or backup before replacing or losing the machine.
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 Production
Note the DOTENV_PRIVATE_KEY_PRODUCTION ends with _PRODUCTION. This instructs dotenvx run to load the .env.production file.
$ echo "HELLO=Ci" > .env.ci
$ dotenvx encrypt -f .env.ci
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ DOTENV_PRIVATE_KEY_CI="<.env.ci private key>" dotenvx run -- node index.js
[[email protected]] injecting env (2) from .env.ci
Hello Ci
Note the DOTENV_PRIVATE_KEY_CI ends with _CI. This instructs dotenvx run to load the .env.ci file. See the pattern?
$ dotenvx set HELLO World -f .env
$ dotenvx set HELLO Production -f .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js
$ DOTENV_PRIVATE_KEY="<.env private key>" DOTENV_PRIVATE_KEY_PRODUCTION="<.env.production private key>" dotenvx run -- node index.js
[[email protected]] 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?
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 World
Encrypted 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 local
If a workspace has its own `.env
Content type
Image
Digest
sha256:84585f3fcβ¦
Size
98.2 MB
Last updated
4 days ago
docker pull dotenv/dotenvx