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:
Cross-platform compatibility, supporting Windows, macOS, Linux, and other environments.
Multi-environment configuration, allowing users to manage different .env files for development, testing, and production.
Encrypted environment variables, ensuring sensitive data is protected both at rest and in transit.
CLI-based interface enabling direct execution of scripts or commands with injected environment variables.
Support for a wide range of programming languages (Node.js, Python, Go, Rust, Java, etc.) and frameworks (Next.js, Rails, Laravel).
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.
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]
$ 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]
$ 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.
$ 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
You can also set DOTENV_CONFIG_QUIET=true.
$ DOTENV_CONFIG_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
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?
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
.env.keys
apps/
web/
index.js
$ cd apps/web
$ dotenvx get HELLO -f ../..
World
$ dotenvx run -f ../.. -- node index.js
[dotenvx@1.X.X] 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
[dotenvx@1.X.X] injecting env (1) from ../../.env.development.local, ../../.env.local, ../../.env.development, ../../.env
Hello development local
If a workspace has its own .env but shares the root .env.keys, point -fk at the root directory:
> 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.
Advanced
> Become a dotenvx power user.
>
CLI 📟
Advanced CLI commands.
run - Variable Expansion
Reference and expand variables already on your machine for use in your .env file.
$ 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}
$ 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}
The reference format is bw:///. The item can be a human-readable Bitwarden search term or an exact item UUID. Quote the dotenv value when the item name contains spaces.
Supported fields are:
username
password
uri
When the vault is locked in an interactive terminal, dotenvx prompts for your Bitwarden master password.
Human-readable names are convenient, but must identify a single item. If multiple items match, Bitwarden returns an error. Use the item UUID when you need an unambiguous reference.
If Bitwarden cannot resolve a reference, dotenvx reports the error and leaves the original bw:// value unresolved.
Use --no-bitwarden to skip Bitwarden resolution intentionally.
$ dotenvx run --no-bitwarden -- node index.js
The same flag is available for dotenvx get and dotenvx validate.
run -f
Run a command using the .env file in a directory. This is useful with monorepos.
$ dotenvx run -f ../.. -- node index.js
[dotenvx@1.X.X] injecting env (1) from ../../.env
Hello World
run - multiple -f flags
Compose multiple .env files for environment variables loading, as you need.
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 --redact
Run any command with real environment variables while automatically redacting their values from stdout and stderr. Keys ending in _PLAIN are left visible.
Redaction 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 --redact -- claude -p
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]
run --redact -- claude
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 -- claude
run --redact -- codex exec
Run 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]
run --redact -- codex
Start a fully interactive Codex session. Codex receives the real values, but any values it prints are redacted.
Override existing env variables. These can be variables already on your machine or variables loaded as files consecutively. The last variable seen will 'win'.
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.
$ 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)
Validation errors are reported without stopping your command. Combine --validate with --strict to exit with code 1 before the command runs.
$ dotenvx run --validate --strict -- node index.js
Any inline comment containing the word optional marks that key as optional. If .env.example is missing, dotenvx reports MISSING_ENV_EXAMPLE. An empty .env.example is valid and declares no required variables.
run --strict
Exit with code 1 if any errors are encountered - like a missing .env file or decryption failure.
$ DOTENV_CONFIG_CONVENTION=nextjs dotenvx run -- 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 -f --convention=nextjs
Run a command using Next.js' convention from a directory. This is useful with monorepos.
$ dotenvx run -f ../.. --convention=nextjs -- node index.js
[dotenvx@1.X.X] injecting env (1) from ../../.env.development.local, ../../.env.local, ../../.env.development, ../../.env
Hello development local
$ NODE_ENV=development DOTENV_CONFIG_CONVENTION=flow dotenvx run -- 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
run --mask
Inject masked values into the command. By default, up to the first six characters are visible.
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.
$ dotenvx set HELLO World --no-armor
◈ encrypted HELLO (.env)
set KEY value --no-create
Do not create a missing .env file.
$ dotenvx set HELLO World -f .env.production --no-create
encrypt
Encrypt 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 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) + 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 locally
Print 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.json
validate
Validate .env file(s) against .env.example without running a command.
Use -f and -fk to validate a specific env file and keys file. The command exits with code 1 when validation fails and prints errors to stderr. On success, it exits with code 0.
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"]
prebuild directory
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
Lock private keys with a local passphrase to keep them protected inside .env.keys.
# example
DOTENV_PRIVATE_KEY=locked:02f5b97ad58b49ae324cd4e7937bc19b251d006b31cacf46f789eeaf03f923cedc:AZIPDxKqjPLiGl5b4CqVGbR3CIBDUcqHthGaoeWLoUvxbTHJkj3jGoGWGaxFSDUJGQUmWDaExRzxKpVydYF_7qiWr1ecqksOFho5t3EMwKbqX2-y-LZO9K3a4SJaYAjDJXpn3NwG4vAt1oLmGA
lock up
Lock a private key in .env.keys with a local passphrase.
After authentication, dotenvx first attempts to store your access token in your operating system's native secret store: macOS Keychain, Windows Credential Manager, or Linux Secret Service. If native secure storage is unavailable, dotenvx falls back to its settings file. This follows a common CLI credential-storage pattern: prefer protected OS storage when available while remaining usable in headless or minimal environments.
armor logout
Log out of Dotenvx Armor.
$ dotenvx armor logout
armor status
Print the current Armor status. It returns on when you are logged in and Armor is enabled; otherwise it returns off.
$ 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 encrypt .env file(s)
decrypt decrypt .env file(s)
keypair [KEY] print public/private keys for .env file(s)
ls [directory] print all .env files in a tree structure
gitignore append to .gitignore
genexample [directory]
generate .env.example
validate validate .env file(s) against .env.example
precommit [directory]
prevent committing .env files to code
prebuild [directory]
prevent including .env files in docker
Professional Security:
lock ⊡ lock private keys with a local passphrase
native ⌥ move private keys into your OS secret store
armor ⛨ move private keys into Dotenvx Armor [www.dotenvx.com/armor]
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
Set a convention when using dotenvx.config(). This allows you to use the same file loading order as the CLI without needing to specify each file individually.
There are global settings available that can be configured as environment variables.
# config
DOTENV_CONFIG_CONVENTION= # set to a default convention like 'nextjs' or 'flow'
DOTENV_CONFIG_IGNORE= # MISSING_ENV_FILE,OTHER
DOTENV_CONFIG_QUIET= # set to "true" to default to --quiet
# armor
DOTENVX_ARMOR_TOKEN= # for api calls
DOTENVX_NO_ARMOR= # set to "true" to turn off Armor support
# 1password
DOTENVX_NO_1PASSWORD= # set to "true" to turn off 1Password support
# bitwarden
DOTENVX_NO_BITWARDEN= # set to "true" to turn off Bitwarden support
BW_SESSION= # set to bitwarden session token to bypass password prompt
Error Codes 🚨
Dotenvx errors begin with a stable code in square brackets like [MISSING_ENV_FILE].
# error codes
1PASSWORD_FAILED= # a value could not be resolved with the 1Password CLI
ACCESS_APPROVAL_TIMEOUT= # an Armor access-approval request timed out
BITWARDEN_FAILED= # a value could not be resolved with the Bitwarden CLI
COMMAND_EXITED_WITH_CODE= # the command run by dotenvx exited with a non-zero status
COMMAND_SUBSTITUTION_FAILED= # a $(command) expression in an environment value could not be evaluated
DECRYPTION_FAILED= # an encrypted value could not be decrypted
FILE_NOT_WRITABLE= # dotenvx could not write to the target file
INVALID_COLOR= # the requested terminal color is invalid
INVALID_CONVENTION= # the requested environment-file convention is invalid
INVALID_PASSPHRASE= # a locked private key could not be unlocked with the supplied passphrase
INVALID_PRIVATE_KEY= # a private key is malformed or otherwise invalid
INVALID_PUBLIC_KEY= # a public key is malformed or otherwise invalid
MALFORMED_ENCRYPTED_DATA= # the encrypted value is malformed
MISPAIRED_PRIVATE_KEY= # a private key does not match the existing public key
MISSING_DIRECTORY= # the requested directory does not exist
MISSING_ENV_EXAMPLE= # the required .env.example file does not exist
MISSING_ENV_FILE= # a requested environment file does not exist
MISSING_ENV_FILES= # no .env* files were found
MISSING_ENV_KEYS_FILE= # the requested .env.keys file does not exist
MISSING_KEY= # a requested environment key does not exist
MISSING_LOG_LEVEL= # the requested log level is not supported
MISSING_PRIVATE_KEY= # the private key required to decrypt a value is missing
MISSING_PUBLIC_KEY= # the public key required to encrypt a value is missing
MISSING_REQUIRED= # validation detail for which required variables are missing; surfaced by VALIDATION_FAILED
MISSING_VALUE= # no value was supplied for a key
NOT_FOUND= # a private key was not found in the native secret store
PRECOMMIT_HOOK_MODIFY_FAILED= # dotenvx could not update the pre-commit hook
VALIDATION_FAILED= # one or more required variables declared by .env.example are missing
WRONG_PRIVATE_KEY= # the supplied private key cannot decrypt the value
Armor ⛨
⛨ ARMORED KEYS: Harden your private keys.
⮕ install [curl -sfS https://dotenvx.sh/armor | sh]
⮕ then run [dotenvx armor login]
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.
Is it safe to commit an encrypted .env file to code?
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.
Why am I getting the error 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