Sign inSign up

dotenv/dotenvx

By dotenv

β€’Updated about 10 hours ago

a secure dotenvβ€”from the creator of `dotenv`

Image
1

100K+

dotenv/dotenvx repository overview

dotenvx

A secure dotenv–from the creator of dotenv⁠.

  • Encrypt
  • Commit
  • Ship

Install⁠ · Quickstart⁠

Β 

⁠Quickstart npm version downloads

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 npm 🌍
npm i -g @dotenvx/dotenvx
dotenvx encrypt

npm installs

Β 

with curl 🌐
curl -sfS https://dotenvx.sh | sh
dotenvx encrypt

curl installs

Β 

with brew 🍺
brew tap dotenvx/brew
brew trust dotenvx/brew
brew install dotenvx
dotenvx encrypt

brew installs

Β 

with docker 🐳
docker run -it --rm -v $(pwd):/app dotenv/dotenvx encrypt

docker pulls

Β 

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 encrypt

github releases

Β 

or windows πŸͺŸ
winget install dotenvx
dotenvx encrypt

Β 

⁠Usage

Encrypt your secrets in .env files. The values become ciphertext and only your private key can unlock them.

$ dotenvx encrypt
β—ˆ encrypted (.env)

To encrypt a fresh plaintext file with a public key created elsewhere:

dotenvx encrypt -f .env.production --with <public-key>

This writes the supplied public key into the file and encrypts its values without creating or storing a private key. If every existing DOTENV_PUBLIC_KEY* entry matches the supplied key, it reuses that key, preserves existing ciphertext, and encrypts new plaintext values. A different public key, or an encrypted: value without a matching declared public key, fails with ALREADY_ENCRYPTED and leaves the file unchanged.

Commit your encrypted .env files with your code. It's safe. Now you can securely share secrets through git.

$ git add .env
$ git commit -m "encrypt .env"

Ship your code and secrets together. Dotenvx uses your private key to decrypt and inject your secrets just-in-time to your code.

$ dotenvx run -- node index.js
⟐ injected env (2) from .env

Β 

⁠Design

Three widely used and proven primitives make up Dotenvx's design. Git, .env, and secp256k1.

We chose git because it is the best way to deliver digital goods. It is the container ship of the digital world. All code travels on its ships. Why not secrets? Why invent an inferior secrets delivery mechanism when the world class one is right there at your fingertips.

We chose .env because it is the open standard for loading secrets into code. It represents environment variables, the core primitive that all secrets end up injected into at runtime of code.

Lastly, we needed to choose an encryption algorithm. Encryption of the values inside the .env file would allow us to place those values with code, leveraging git's distribution advantages while still following the twelve-factor config – by separating the decryption key from the code (environment).

It was important that we choose a proven, simple, asymmetric standard with small keys. We chose secp256k1, battle-tested by Bitcoin for more than seventeen years. We made cryptography an opinionated choice so developers wouldn't have to. Encryption just worked.

The result is a system built entirely from primitives, developers already understand, and infrastructure they already use. Secrets can travel with code without becoming part of the code. Git distributes them, .env defines how apps consume them, and secp256k1 keeps their values unreadable. The kicker, all this works by setting a single private key on your infrastructure - no more risky async coordination of secrets, no more centralized downtime risk. Your secrets are always just there with your code, ready to be unlocked at runtime.

Read the whitepaper →⁠

Β 

⁠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
> :-D

see quickstart guides⁠

More examples

Claude πŸ€–

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 spec
$ dotenvx run -- claude -p 'Run `dotenvx get HELLO` and echo back just Hello VALUE' --dangerously-skip-permissions
Hello [REDACTED]

see Claude redaction guide⁠

Codex ✨

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 spec
$ dotenvx run -- codex exec 'Run `dotenvx get HELLO` and echo back just Hello VALUE' --skip-git-repo-check
Hello [REDACTED]

see Codex redaction guide⁠

Cursor πŸ–±οΈ

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 spec
$ dotenvx run -- agent -p --force 'Run `dotenvx get HELLO` and echo back just Hello VALUE' --output-format text
Hello [REDACTED]

see Cursor redaction guide⁠

1Password πŸ”

Run with secrets resolved directly from 1Password.

$ echo "HELLO=op://Personal/hello/password" > .env
$ dotenvx run -- sh -c 'echo Hello $HELLO'
Hello World

see 1Password guide⁠

Bitwarden πŸ›‘οΈ

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.

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
Astro πŸš€

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⁠

Expo 🧭

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⁠

Next.js β–²

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.

Cloudflare Workers ⛅️
$ 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)",
}
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
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.

Python 🐍
$ echo "HELLO=World" > .env
$ echo 'import os;print("Hello " + os.getenv("HELLO", ""))' > index.py

$ dotenvx run -- python3 index.py
Hello World

see extended python guide⁠

PHP 🐘
$ echo "HELLO=World" > .env
$ echo '<?php echo "Hello {$_SERVER["HELLO"]}\n";' > index.php

$ dotenvx run -- php index.php
Hello World

see extended php guide⁠

Ruby πŸ’Ž
$ echo "HELLO=World" > .env
$ echo 'puts "Hello #{ENV["HELLO"]}"' > index.rb

$ dotenvx run -- ruby index.rb
Hello World

see extended ruby guide⁠

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

see extended go guide⁠

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

see extended rust guide⁠

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
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 ["/usr/local/bin/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 }}

see github actions guide⁠

Platforms
# heroku
heroku buildpacks:add https://github.com/dotenvx/heroku-buildpack-dotenvx

# docker
RUN curl -fsS https://dotenvx.sh | sh

# vercel
npm install @dotenvx/dotenvx --save

see platform guides⁠

Process Managers
// pm2
"scripts": {
  "start": "dotenvx run -- pm2-runtime start ecosystem.config.js --env production"
},

see process manager guides⁠

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

[[email protected]] injecting env (1) from .env.production
Hello World
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
[[email protected]] 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
[[email protected]] injecting env (1) from .env
DATABASE_URL postgres://yourusername@localhost/my_database

Β 

⁠Multiple Environments

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
[[email protected]] 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,.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.

`--overload` flag
$ 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.

`--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
[[email protected]] 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
[[email protected]] 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

You can also set DOTENV_QUIET=true.

$ DOTENV_QUIET=true dotenvx run -f .env.production -- 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)

Β 

⁠Encryption

Add encryption to your .env files with a single command. Use dotenvx encrypt.

$ dotenvx encrypt
β—ˆ encrypted (.env)

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
[[email protected]] 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
[[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.

`.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
[[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?

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
[[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?

use directories with monorepos

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
  apps/
    web/
      index.js
$ dotenvx encrypt
$ cd apps/web

$ dotenvx get HELLO -f ../..
World

$ dotenvx run -f ../.. -- node index.js
[[email protected]] injecting env (1) from ../../.env
Hello World

With the private key in your OS secret store, dotenvx finds it automatically from any workspace on the same machine.

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

For a workspace with its own encrypted .env, run from that workspace:

$ dotenvx run -- node index.js
`--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 m

Tag summary

Content type

Image

Digest

sha256:048159a8b…

Size

102.1 MB

Last updated

about 10 hours ago

docker pull dotenv/dotenvx