dotenvx: Encrypted .env Files for JavaScript Apps
April 4, 2026 | 7 minutesIn This Post
If you've used dotenv to manage environment variables in JavaScript projects, you've probably run into its biggest limitation: .env files sit in plaintext on disk. That was always a tradeoff, but it's become a real problem now that AI coding agents like Claude Code, Cursor, and GitHub Copilot routinely read your project files as context. A plaintext .env file means your secrets can end up in model context without you realizing it.
dotenvx is a newer tool from the same creator that solves this. It encrypts your .env files so you can commit them to git, uses public-key cryptography to control who can decrypt them, and introduces environment-specific private keys like DOTENV_PRIVATE_KEY_CI for CI/CD pipelines. Let's look at how it works and how to set it up.
How dotenvx Differs from dotenv
dotenv reads a plaintext .env file and loads its values onto process.env. It works, but you can't commit .env files because they contain secrets in plaintext. It's hard to securely share secrets with your team without sharing them in plaintext by email or Slack. Plus, since they're just files on your local machine, AI coding agents can read them just like any other file.
dotenvx keeps the same .env interface that developers (and agents) already expect, but adds encryption on top. The key differences are that:
- Encrypted values are stored in the
.envfiles themselves, so you can commit them to git - It uses public-key cryptography (secp256k1, the same curve Bitcoin uses) to protect each secret
- Environment-specific keys let you easily scope decryption access:
DOTENV_PRIVATE_KEYfor development,DOTENV_PRIVATE_KEY_PRODUCTIONfor production,DOTENV_PRIVATE_KEY_CIfor CI/CD - It has cross-platform and cross-language support so you can use it with Node.js, Python, Ruby, Go, Rust, and more
Getting Started with dotenvx
Install dotenvx with one of these methods:
1# npm (local to project) 2npm install @dotenvx/dotenvx --save 3 4# npm (global) 5npm i -g @dotenvx/dotenvx 6 7# Homebrew 8brew install dotenvx/brew/dotenvx 9 10# Shell script 11curl -sfS https://dotenvx.sh | sh
Then use it to run your app, loading .env values automatically:
1echo "HELLO=World" > .env 2echo "console.log('Hello ' + process.env.HELLO)" > index.js 3 4dotenvx run -- node index.js 5# Hello World
The syntax is dotenvx run -- <your command>. Everything after -- is your normal startup command.
Encrypting Your .env Files
This is where dotenvx really shines. Run a single command to encrypt your .env file in place:
1dotenvx encrypt
This does two things:
- Encrypts the values in your
.envfile, replacing plaintext with encrypted strings - Generates a
.env.keysfile containing the private key needed for decryption
After encryption, your .env file will look something like this:
1#/-------------------[DOTENV_PUBLIC_KEY]--------------------/ 2#/ public-key encryption for .env files / 3#/ [how it works](https://dotenvx.com/encryption) / 4#/----------------------------------------------------------/ 5DOTENV_PUBLIC_KEY="038759c073282f2efa6c5ffea8f66ad9cf0de7a855df8db242771f44d7472b63cb" 6 7# .env 8HELLO="encrypted:BGMyAFNH6UjetjWsYHUkbndQosw..."
And your .env.keys file:
1DOTENV_PRIVATE_KEY="bd7c50b352ce23973ec9db355d70212305a0baaade92f0165f02915b213bfbe2"
What to Commit (and What Not To)
Your encrypted .env file is safe to commit to git, making it easy to share with your team through git.
Your .env.keys file is a different story. It contains the private key that can decrypt everything. Do NOT commit .env.keys. Add it to your .gitignore immediately:
1echo ".env.keys" >> .gitignore
Store the private key somewhere secure that your deployment environment can access, like your CI provider's secrets manager (e.g., GitHub Actions secrets, Vercel environment variables, or AWS Secrets Manager).
How Decryption Works at Runtime
When you run dotenvx run -- node index.js, dotenvx looks for the private key in two places:
- The local
.env.keysfile (for local development) - The
DOTENV_PRIVATE_KEYenvironment variable (for CI/CD and production)
If it finds the key, it decrypts your .env values in memory and injects them into process.env. This means that your application code doesn't need to change at all. It also means that if you're already using dotenv in your codebase, then dotenvx is a drop-in replacement.
Using dotenvx set to Add New Secrets
Instead of manually editing and re-encrypting, you can add encrypted values directly:
1dotenvx set API_KEY "sk-my-secret-key"
This encrypts the value and updates the .env file in one step.
Multiple Environments
dotenvx supports multiple environment files out of the box. Each gets its own encryption key pair:
1# Create and encrypt a production .env 2echo "DATABASE_URL=postgres://prod-server/mydb" > .env.production 3dotenvx encrypt -f .env.production 4 5# Create and encrypt a CI .env 6echo "DATABASE_URL=postgres://test-server/mydb" > .env.ci 7dotenvx encrypt -f .env.ci
Each file gets its own keys in .env.keys:
1DOTENV_PRIVATE_KEY="..." 2DOTENV_PRIVATE_KEY_PRODUCTION="..." 3DOTENV_PRIVATE_KEY_CI="..."
To run with a specific environment:
1dotenvx run -f .env.production -- node index.js
Or set the matching private key as an environment variable, and dotenvx figures out which file to decrypt:
1DOTENV_PRIVATE_KEY_PRODUCTION="your-key-here" dotenvx run -- node index.js
The naming convention is the magic: DOTENV_PRIVATE_KEY_CI automatically maps to .env.ci, DOTENV_PRIVATE_KEY_PRODUCTION maps to .env.production, and so on.
Setting Up DOTENV_PRIVATE_KEY_CI for GitHub Actions
This is the workflow for using encrypted environment variables in CI. The idea is that you commit your encrypted .env.ci file to the repo, and the CI runner decrypts it at runtime using a secret you store in GitHub (or your CI provider).
1. Create and encrypt your CI environment file
1echo 'DATABASE_URL="postgres://test-server/mydb"' > .env.ci 2echo 'API_KEY="test-api-key"' >> .env.ci 3dotenvx encrypt -f .env.ci
2. Copy the CI private key from .env.keys
Look for the DOTENV_PRIVATE_KEY_CI value in your .env.keys file.
3. Add it as a GitHub Actions secret
Go to your repo's Settings > Secrets and variables > Actions and create a new secret called DOTENV_PRIVATE_KEY_CI with the private key value.
4. Update your workflow
1name: build
2on: [push]
3jobs:
4 build:
5 runs-on: ubuntu-latest
6 steps:
7 - uses: actions/checkout@v4
8 - uses: actions/setup-node@v4
9 with:
10 node-version: 20
11 - run: curl -sfS https://dotenvx.sh/install.sh | sh
12 - run: dotenvx run -- node index.js
13 env:
14 DOTENV_PRIVATE_KEY_CI: ${{ secrets.DOTENV_PRIVATE_KEY_CI }}
When the workflow runs, dotenvx sees the DOTENV_PRIVATE_KEY_CI environment variable, uses it to decrypt .env.ci, and injects the values into your process. You'll see a log line like injecting env (2) from .env.ci confirming it worked.
This same pattern works for GitLab CI, CircleCI, or any CI provider that supports secret environment variables. Just set DOTENV_PRIVATE_KEY_CI as a secret in whatever platform you use.
Why This Matters for AI Coding Agents
This is the part that wasn't relevant when dotenv was first created but has become critical. AI coding agents like Claude Code, Cursor, and GitHub Copilot read your project files to build context. If your .env file is plaintext, those secrets are going straight into the model's context window.
With dotenvx, the encrypted .env file is committed to your repo, but the values are gibberish without the private key. An AI agent reading your project sees:
1API_KEY="encrypted:BGMyAFNH6UjetjWsYHUkbndQosw..."
Instead of:
1API_KEY="sk-live-abc123-my-real-key"
This is a meaningful improvement over the traditional approach of just .gitignore-ing your .env file, because:
- Agents read all project files, not just what's in git. A
.gitignore'd.envfile on disk is still readable, so with plain dotenv, your secrets are fully exposed. - Encrypted values are useless without the private key. An agent can still read
.env.keysif it's on disk, so this isn't bulletproof on its own. But it reduces the attack surface: instead of every secret sitting in plaintext, there's a single key file to protect. - CI environments are scoped, so even if an agent has access to your
DOTENV_PRIVATE_KEY, it can't decrypt your production secrets if those useDOTENV_PRIVATE_KEY_PRODUCTION.
The dotenvx team has also been building Agentic Secret Storage (AS2), which takes this further by giving each agent its own cryptographic identity for accessing secrets without any human-in-the-loop OAuth or API key flows. It's still early, but it points to where secret management is heading as AI agents become a standard part of development workflows.
Migrating from dotenv
If you're already using dotenv, the migration is straightforward:
- Install dotenvx:
npm install @dotenvx/dotenvx --save - Encrypt your existing
.envfile:dotenvx encrypt - Commit the encrypted
.envfile and add.env.keysto.gitignore - Update your start script from
node -r dotenv/config index.jstodotenvx run -- node index.js - Remove the
dotenvpackage:npm uninstall dotenv
Your code doesn't need to change at all. You still read values from process.env the same way. The only difference is how they get there.
Wrapping Up
dotenvx solves two problems that have gotten worse over time: sharing secrets across a team without insecure side channels, and keeping secrets out of AI model context. The encrypted-file-in-git approach is simpler than setting up a full secrets manager, and the environment-specific key convention (DOTENV_PRIVATE_KEY_CI, DOTENV_PRIVATE_KEY_PRODUCTION) makes it easy to scope access for CI/CD pipelines.
If you're starting a new project or looking to tighten up an existing one, it's worth the switch. Check out the dotenvx docs for more details, including guides for platforms beyond GitHub Actions.
