It's a fairly common practice to run a project locally and use a .env
file to load environmental variables from it. As long it's for local development, that's absolutely fine, then when running the code in any live or test environments you should fallback to actual system variables, never, ever, use .env
files in those cases.
But while the above is generally a standard and acceptable practice, it has a very simple issue.
Variables that represents secrets, are not so secret in your local environment, and in the era of AI coding tools, you might expose keys that could be potentially be end up in some LLM and eventually exposed to the internet if you are not careful enough.
I get it, it's a bit of an extreme example, but it could theoretically happen!
In Node.js, but also in other languages, you often use a variation of a dotenv
package to load those secrets in your code, but now there's a better alternative: dotenvx!
How to install it
# As a dependency of your project
npm install @dotenvx/dotenvx --save
# globally
npm install --global @dotenvx/dotenvx
# More ways can be found at https://dotenvx.com/docs/install
Some key differences
What it differs from the other non-x package, is the fact that the latter can encrypt your secrets, using a public and private keys.
And instead of being a Node.js package exclusively, you can use it to run a large number of languages, as you can run it as follows:
dotenvx run -- <whatever>
# example
dotenvx run -- node main.js
# or Python
dotenvx run -- python3 main.py
# or Rust
dotenvx run -- cargo run
You get the gist, you can run pretty much (almost) anything with it.
Another advantage is that you don't really need to add it as a dependency in your project, unlike dotenv
, you can just run it with npx to run your command and inject the environmental variables, like so:
echo "HELLO=World" > .env
echo "console.log('Hello ' + process.env.HELLO)" > index.js
node index.js
# Hello undefined
npx @dotenvx/dotenvx run -- node index.js
# Hello World
But if you prefer, you can use it as a drop-in replacement for dotenv
:
// Before
import { config } from 'dotenv';
// After
import { config } from '@dotenvx/dotenvx';
config();
How to use it
One thing I want to clarify, you don't encrypt the entire .env
file, you just encrypt variable by variable, and you don't need to encrypt them all, just the ones you want to keep secure.
So stuff that are not secrets, but just configuration settings, don't really need to be encrypted.
But let's set it in action to better understand how it works.
Let's create a simple .env
with a few variables and normally you might have something like this:
PROJECT_NAME="Steve"
PORT=1234
SECRET_KEY="supersecret"
Now, we don't really care if PROJECT_NAME
or if the PORT
are left in clear, but we really want to protect that secret.
What do you need to do is the following:
npx @dotenvx/dotenvx set SECRET_KEY "supersecret" -f .env
With the above a few things happened.
.env.keys
created
A new file .env.keys
is created. This create the private key that it will be used to encrypt/decrypt our secrets. This file must be added to .gitignore.
A public key is added
On top of your .env
file you will see something like this:
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY="0246dd40acee998b5bbc850c507dfaca0a49245c4da6eb9b0c9ae4219e5d83d292"
This is the public key used in conjunction with the secret one, that dotenvx
will use to decrypt the secrets when using dotenvx run -- command
.
Your secret key is now encrypted
Where before you had a clear string for your secret, now you have the following:
PROJECT_NAME="Steve"
PORT="1234"
SECRET_KEY="encrypted:BMQJJ2bEACUIPad/jtRIyDi6m0yBYhlcUBFREgvBCTYuLajcCtyJ3hB0PLMnBDY4CWxHphnuCAgobqyzL+LucTV8TAukr8rIPIr8D8me4LIfQu8WHqYj5DXicDKE9U35pOHbFWDbBE3iv2JR"
As you can see, only the SECRET_KEY
variable is encrypted, while the other variables are still in clear text.
Working with multiple environments
One really nice feature of dotenvx
is how it handles different environments. You can have separate .env
files for different environments and encrypt secrets differently for each one.
For example, you might have:
-
.env
for local development -
.env.production
for production secrets -
.env.staging
for staging environment
# Encrypt secrets for different environments
npx @dotenvx/dotenvx set SECRET_KEY "local-secret" -f .env
npx @dotenvx/dotenvx set SECRET_KEY "prod-secret" -f .env.production
npx @dotenvx/dotenvx set SECRET_KEY "staging-secret" -f .env.staging
Then you can run your application with the appropriate environment:
# Local development
npx @dotenvx/dotenvx run -- node app.js
# Production
npx @dotenvx/dotenvx run -f .env.production -- node app.js
# Staging
npx @dotenvx/dotenvx run -f .env.staging -- node app.js
A real-world example
Let's say you're building an Express.js application that connects to a database and uses an API key. Your app.js
might look like this:
// app.js
import express from 'express';
import { config } from '@dotenvx/dotenvx';
// Load environment variables
config();
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.json({
message: 'Hello World!',
environment: process.env.NODE_ENV,
// Never log secrets in real applications!
hasDbUrl: !!process.env.DATABASE_URL,
hasApiKey: !!process.env.API_KEY
});
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Your .env
file would contain:
NODE_ENV="development"
PORT=3000
DATABASE_URL="encrypted:BK2Jsd9f8h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3"
API_KEY="encrypted:AL9Kme4g7j2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1"
Important security considerations
While dotenvx
is much more secure than plain .env
files, there are still some best practices to follow:
Always add
.env.keys
to your.gitignore
- This file contains your private key and should never be committed to version control.Share the
.env.keys
file securely - When working in a team, you'll need to share the private key securely (through encrypted channels, password managers, or secure key management systems).Rotate your keys regularly - Just like any other secret, consider rotating your encryption keys periodically.
Still use proper environment variables in production - Even with
dotenvx
, you should still use proper environment variables or secret management systems in production environments.
What about CI/CD?
In your CI/CD pipeline, you can use dotenvx
by either:
-
Storing the private key as a secret in your CI/CD system and creating the
.env.keys
file at runtime -
Using a dedicated
.env.ci
file with encrypted secrets specific to your CI environment
# In your CI/CD pipeline
echo $DOTENV_PRIVATE_KEY > .env.keys
npx @dotenvx/dotenvx run -- npm test
Conclusions
As you can see, dotenvx
is a much better and more secure alternative to traditional .env
files. It provides encryption for sensitive data while maintaining the simplicity of environment files, works across multiple programming languages, and offers flexible deployment options.
The fact that you can selectively encrypt only the secrets you care about, while leaving configuration values in plain text, makes it practical for real-world use.
I highly recommend you check out the project site at https://dotenvx.com/ and give it a try in your next project.
Disclaimer: AI was used to reformat this article and correct errors
Top comments (0)