DEV Community

How to manage secrets and configs using dotenv in Node.js and Docker

What is dotenv?

It's a javascript package that reads KEY=VALUE from a .env file (example below) and sets each pair as env variables.

// 1. Create a .env file:
DB_ADMIN_USERNAME=techbos
DB_ADMIN_PASSWORD=Pa$$w0rd

// 2. In your node app, load dotenv to read .env and set env variables when app starts
require('dotenv').config();

// 3. You can now use the env variables in your app
connectDatabase({
  username: process.env.DB_ADMIN_USERNAME, //techbos
  password: process.env.DB_ADMIN_PASSWORD, // Pa$$w0rd
});

Why should I use dotenv?

dotenv allows you to separate secrets from your source code. This is useful in a collaborative environment (e.g., work, or open source) where you may not want to share your database login credentials with other people. Instead, you can share the source code while allowing other people to create their own .env file.

It's also useful for dynamically configure your app without changing the source code. For example, you can set DB_URL to a dev database for local development. Or, if you want to print logs to console for local development, but not in prod, you can do:

// .env file
ENV=dev

// in your app
if (process.env.ENV === 'dev') console.log(...);

For real-world applications that are deployed to hundreds or even thousands of instances, using dotenv (or use other similar tech) allows all instances to share the same source code, while each having a different .env file so they can use different configurations, e.g., connecting to different databases or writing logs to different endpoints.

How to setup dotenv in my project?

1. Preload dotenv in node.js

Start your node app with dotenv preloaded so you don't even need to require dotenv in your source code.

// use this
node -r dotenv/config index.js
// instead of
require('dotenv').config();

2. Add .env to .gitignore

You should never share .env file in source control. Instead, create a separate private repo to store your own .env files, while sharing the rest of the source code with others.

//.gitignore
.env

3. Create as many .env as you like

It's typical to have multiple .env files. E.g., you may have one for dev, one for stage and one for prod. After you checkout the source code, copy over the .env file for your environment and start the app.

3. Start your app

// E.g., use with babel-node for local development
nodemon --exec babel-node -r node_modules/dotenv/config src/index.js
// E.g., run with node in production
node -r dotenv/config src/index.js

4. Run in docker

// Dockerfile
CMD node -r dotenv/config ./build/index.js

Use dotenv-safe

A good alternative to dotenv is dotenv-safe, which is identical to dotenv except that it allows you to create a .env.example file. This serves two purposes:

  1. It allows you to specify all required env variables in the example file. At runtime the package checks if a .env file has all the required variables defined, and throws if not.
  2. You can share the .env.example file with others so everyone knows what env variables are required.

Use in Docker

In some cases, building the .env file into a docker image is considered a bad practice because if the image is shared with others, the secrets can be leaked.

To fix that, you can map a .env file into docker at runtime:

volumes:
  - /local/file/path/to/.env:/app/.env

Another approach is to use the env_file docker option. You can read more here

Top comments (3)

Collapse
 
pcleddy profile image
Paul Charles Leddy

Brilliant. Again!

Collapse
 
geekyayush profile image
Ayush Somani

Thank you very much. The .env volume mapping helped to solve the issue I was facing.

Collapse
 
mgrachev profile image
Grachev Mikhail • Edited

In addition to using environment variables I can recommend the tool github.com/dotenv-linter/dotenv-li....
It’s a lightning-fast linter for .env files. Written in Rust.