DEV Community

Alessio Michelini
Alessio Michelini

Posted on • Edited on

Secure your .env with dotenvx

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}`);
});
Enter fullscreen mode Exit fullscreen mode

Your .env file would contain:

NODE_ENV="development"
PORT=3000
DATABASE_URL="encrypted:BK2Jsd9f8h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3jk2h3"
API_KEY="encrypted:AL9Kme4g7j2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1m2l1"
Enter fullscreen mode Exit fullscreen mode

Important security considerations

While dotenvx is much more secure than plain .env files, there are still some best practices to follow:

  1. Always add .env.keys to your .gitignore - This file contains your private key and should never be committed to version control.

  2. 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).

  3. Rotate your keys regularly - Just like any other secret, consider rotating your encryption keys periodically.

  4. 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:

  1. Storing the private key as a secret in your CI/CD system and creating the .env.keys file at runtime
  2. 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
Enter fullscreen mode Exit fullscreen mode

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)