Using environment variables to store secrets instead of writing them directly into your code is one of the quickest and easiest ways to add a layer of protection to your projects. There are many ways to use them, but a properly utilized .env file is one of the best, and I’ll explain why.

They’re project scoped

Environment variables are a part of every major operating system: Windows, MacOS, and all the flavors of *nix (Unix, BSD, Linux, etc.). They can be set at an operating system level, user level, session level… It gets complicated, and where/how you define them matters to the scope in which they can be accessed.

This variety of scopes also creates the distinct possibility of variable collisions. If you’re looking for an environment variable named API_KEY, that could be getting re-defined in each scope, and if you’re not steeped in that OS, it’s extra work to be sure you’re not clobbering something someone set at a different scope that some other app or service needs.

.env files are only consumed at runtime and only in the context of the app that’s consuming them. That prevents them from clobbering any other environment variables on the system that might be consumed outside your app.

They can be "ignored"

If you’re working on a JavaScript application in Node, you can’t ignore your index.js file in the version control system. It contains essential code. But you can set your .gitignore file to have the Git system ignore your .env file. If you do that from the inception of your repository, you won’t commit secrets to the project’s Git history.

A better option is to include a .sample.env file that sets the variable names, but only includes dummy data or blanks. People cloning/forking and using the repository can get the secrets via another route, then cp .sample.env .env (in a terminal), and assign the real values to the proper variables in the ignored .env file.

They’re relocatable

While most systems will default to looking for the .env file in the root of the app’s primary directory, you can always have it a level or two higher. So, if for example, a server configuration error or code bug leaves it possible to view all the files at the root of your web app as a directory, the .env will not be there for easy pickings.

This is not an uncommon practice. SSH keys are stored by default at ~/.ssh (a "hidden" subdirectory of the home directory of the user) on Windows, Mac, and Linux. You do not need to move them into the root directory of a project that uses them.

A quick .env demo in Node

Let’s say your working directory for the app you’re building is ~/Documents/work/projects/games/tictactoe and tictactoe is the root directory for the app you’re building and your Git repository. You can put the .env in the next directory up, games. And while we generally call the file type .env, you can call it .toecreds if you want to make it a distinct file that other processes would never even think to touch. We'll use that in the demo.

Here’s how you’d do that in Node.js.

  1. In your games/tictactoe directory, npm init (go with the defaults) and then npm install dotenv.
  2. Create your .toecreds file in the games directory.
  3. Fill the .toecreds file with information in the following format: VARIABLE_NAME=VALUE (no spaces). You can also start a line with # for a comment. Here's some sample code:  
# Leaderboard SaaS
LEADERBOARD_ENDPOINT=https://example.com/leaderboard/v1
LEADERBOARD_KEY=jknfwgfprgmerg…

.toecreds content

  1. At the top of your index.js (or whatever file is your launchpoint) in games/tictactoe, include the following lines:
require('dotenv').config({ path: '../.toecreds' });
console.log(process.env.LEADERBOARD_ENDPOINT);

index.js content

Run your index.js (type node index.js at a command prompt in your games/tictactoe directory) and the endpoint URL will be output to the terminal. Meanwhile, the environment variables you set in it will not be available from the terminal.

Try adding a long timeout to the script and then running node index.js & to return control back to the terminal after invoking the script. While the script is running in that shell session, the environment variables available to the shell still do not contain the secrets. The secrets are scoped to your running application.

You can have dev, test, and prod credential sets, having your CI/CD tooling pull the correct keys for the deployment target from a secrets manager and write the .toecreds (or .env) file to the same relative directory.

And there you have it

The use of a .env file helps you keep your app's secrets from ever being committed to your version control and provides an additional layer of protection against your secrets being discovered by hackers or other prying eyes. It's a great addition to your developer / dev-ops toolbox.