If you’ve worked on any form of application development, you must have already heard of the term “environment variables.” Environment variables are used to store app secrets and configuration data, which are retrieved by your running app when needed.

Environment variables add dynamicity to your static code base; you can switch between internal/external resources based on the value of the environment variable passed to your app.

This article will break down environment variables for you. You will learn some popular ways to create and manage environment variables and understand when to use them. By the end of this article, you’ll be able to effectively and confidently use environment variables in your applications.

Without further ado, let’s begin!

What Is an Environment Variable?

Reiterating the explanation above, environment variables are variables available to your program/application dynamically during runtime. The value of these variables can come from a range of sources — text files, third-party secret managers, calling scripts, etc.

What’s important here is the fact that the value of these environment variables is not hardcoded in your program. These are truly dynamic and can be changed based on the environment that your program is running in.

Types of Environment Variables

There are three prominent types of environment variables in a Windows-based computer system, and each of them has its use cases. Let’s discuss them one by one.

1. System Environment Variables

System environment variables reside on the topmost root of the system and are the same for all processes running in a system under all the user profiles of the system. Your operating system/system administrator usually sets these, and you’re rarely required to fiddle with them.

One of the most common uses of a system environment variable is setting up a PATH variable to a global package/library to be used by all users in the system.

2. User Environment Variables

User environment variables are those that are local to a user profile in Windows systems. These variables are used to store user-specific information such as the path to a local installation of libraries that are not to be used by all users, values specific to programs installed only for specific users, etc.

You do not need the system administrator to make changes to these variables; you can do that yourself as a user. These variables are helpful when implementing local changes in your system without affecting other users.

3. Runtime/Process Environment Variables

Runtime environment variables are further narrowed down only to the runtime or the process that they are associated with. These are usually set by the parent process that creates the new process and is accompanied by the system and user environment variables as well.

You can use terminal scripting to create and store these variables on the fly. Runtime variables are usually not permanent unless scripted, and you need to define them whenever you start a new process.

Environment Variables in Unix-based Systems

Unlike Windows systems, Unix-based systems do not have three layers of environment variable types. In such systems, everything is stored under a var object and can be accessed/modified by the running program.

If you need to set some environment variables as default to be loaded each time any program runs on your system, you need to define them in files such as .~/bashrc or ~.profile that are loaded when the system boots up.

Environment Variables vs Pseudo-environment Variables

A separate line of dynamic environment variables is available in Windows and DOS-based systems, known as pseudo environment variables. These are not statically assigned pairs of keys and values, but rather dynamic references that return various values when queried.

While you can assign a value to an environment variable manually by using the SET command or its equivalent, you can not assign values to pseudo environment variables. There is a fixed list of such variables, and you can use them in your code to make your job easier. Some popular use cases include %CD% for the current directory and %TIME% for the current time.

Why Should You Use Environment Variables?

Now that you understand what an environment variable is and its various types, it’s time to know why you should extensively use it in your apps.

Separation of Concerns

One of the most important reasons why you should use environment variables in your applications is to adhere to a popular and useful design principle — separation of concern. The design principle states that computer programs should be divided into distinct sections to manage them efficiently. Each section should be based on one of the program’s primary concerns, and there should be minimal coupling between such sections.

You can consider application configuration as one of such concerns; hence it needs to be separated from the main program. One of the best ways to do it is to store it in an external file and inject it as and when needed. Environment variables help you isolate critical application configuration data using env files or remote variable stores. This way, your developers are only provided with the information they need.

Maintaining Independent Configuration Sets Across Environments

Apart from isolating app configurations from your source code, you also need to be able to switch between sets of configuration data easily. If you choose to hard-code your app configuration values in your source code, it can become almost impossible to replace those values based on external conditions such as deployment environments.

The usage of environment variables helps you decouple your configuration data from your code and standardize the way this information is provided to your app, which enables you to modify/exchange the information being provided on the fly.

Securing Secrets

Your app secrets fall under sensitive information. If wrong people get their hands on them, they can access your app’s internal architecture and third-party resources. Common examples include AWS keys and system account details. Unauthorized access to these keys can lead to loss of money and application data. Hackers can even go so far as to restrict your app from working normally.

Therefore it is vital that you protect these secrets. Leaving them lying around in your codebase can result in all of your developers gaining access to them. If you do not follow proper code obfuscation methods, your apps can be reverse engineered to retrieve the keys lying among your code. Isolating these secrets via environment variables can prevent such scenarios from happening.

Example Use Cases of Environment Variables

Now that you have a clear idea of how environment variables work and how you should use them effectively, here are some common scenarios where you can use environment variables:

  • Environment Type: Environment variables are often used to store the name of the environment in which the app is currently running. The app’s logic can use this value to access the right set of resources or enable/disable certain features or sections of the app.
  • Domain Name: The domain name of an application can vary based on its environment. Isolating it also helps you easily make changes to your app’s domain name without searching for its occurrences throughout the codebase.
  • API URLs: Each environment of your app can have APIs deployed in different environments as well.
  • Private Keys: Keys to paid services and resources need to be isolated from the app’s source code so that they do not accidentally fall into the wrong hands.
  • Service Account Numbers, etc.: You can vary other system-specific information, such as service account numbers, keytabs, etc., based on the app’s environment for resource management and monitoring.

How To Store Environment Variables

Now that you understand why environment variables are important, it is time to look at how you can store and access them in your application. Discussed below are three different yet popular ways of managing environment variables in an application.

Using .env Files

.env files are undoubtedly the easiest and most popular ways of managing environment variables. The idea here is simple—you store your environment variables in a file named .env at the root of your project. The application queries for the variables in this file and loads them for use during runtime. Here’s what a typical .env file looks like:

VAR_UNO=SOME_KEY_HERE
VAR_DOS=SOME_OTHER_KEY_HERE

.env files also enable you to define sets of environment variables and access them based on your app’s runtime environment or other factors. Instead of saving the file as simply .env, you can make more than one file and keep them as .env.dev and .env.prod. Inside these files, you can define the same sets of variables but with varying values, depending on the environment.

Example Template

When working with .env files, it is not recommended to add them to your version control system (more on this later). Therefore you should define a “.env.template” in your project so that developers can follow the template and create a .env file for themselves locally.

Here’s what a .env.template file would look like:

VAR_UNO= # Your value here
VAR_DOS= # Your value here

It doesn’t have to follow any conventions as long as it is self-explanatory for your development team. To simplify this process, you could also use packages such as env-template.

Pros

Here are some reasons you should consider using .env files to manage environment variables in your app.

Simple To Use

This method is the simplest in the line of environment variable management techniques. All you need to do is create a plain text file that contains your secrets and store it at the root of your project.

Switching environments is as simple as changing the env file itself. You can store multiple files by the names .env.dev, .env.prod, .env.uat, etc., and configure your source code to access these files based on the environment that it is being run under.

Local Access

You can easily set up .env files in a local development environment. Unlike platform-native variable managers, you do not need to deploy your app to leverage the environment variables functionality. Compared to secret managers, .env files are easier to set up locally, and there is no network dependency for accessing your app secrets.

Open Source Support

There are numerous open-source packages to help you load and manage app secrets from env files. You do not need to depend on paid services, nor are your options limited when it comes to app secret management. There is a wide range of third-party open-source libraries to help you manage your env files. Some popular/useful examples are dotenv, env-template, and cross-env.

Cons

Here are some disadvantages of env files you should know before using them in your projects.

Formatting

.env files store your app secrets in the form of key-value pairs. The usual format for storing environment variables in a .env file is:

Key1=Value1

You need to strictly stick to this format for your app to be able to read your app secrets successfully. If you make one small mistake somewhere between tens or hundreds of lines of environment variables, the entire file might not be parsed, and your program will throw unrelated errors throughout. The fact that there is a parsing error with your .env file might not even be highlighted. This is why you need to be careful while using .env files.

Prone to Accidental Secret Leakage While Sharing/Storage

Since .env files are plain text files, they are vulnerable to accidental exposure when stored on a shared hard drive or sent over via an unsecured network. Therefore you need to take special care to not leak out your app secrets when you’ve stored them using .env files.

Using Platform-Native Variable Storage

Another popular option for storing environment variables is to rely on your deployment platform’s variable storage. Most deployment environments, such as Heroku, AWS, Netlify, etc., provide a space for users to upload secrets which are later injected into the runtime of your application. You can check your deployment platform’s documentation to know if they support it and how to get started with it.

Here’s a quick look at Netlify’s environment variable manager:

Screenshot of Netlify's environment variables manager showing an editable list of keys and values
Netlify’s Environment Variables Manager.

Pros

Here’s why you should consider using platform-native variable storage solutions.

Highly Secure

Since this option is managed entirely by your deployment platform, it is bound to be more secure than storing your secrets in a plain text file. You can control who gets access to the variables manager, and you can rest assured that the secrets will never get accidentally pushed to your VCS.

Easy To Update

Updating the environment variables when they’re stored independently is simpler—you do not need to edit your source code and make a new release for it. You can simply change the values in the platform and rebuild your project. It will receive the new values the next time it is started.

Formatting woes are gone, too, since most platform-specific deployment managers lint the keys as you enter them. In cases such as Netlify’s, you get to enter the secrets in a pre-formatted form, eliminating the chances of making a formatting mistake.

Enables Collaboration

Since deployment platforms can be accessed by your entire team, you can easily share the secrets with the right people without having to send over text files via the internet. You can control who gets to access the variable manager (in most cases) and use it as a central repository for your app secrets.

Cons

While platform-native variable managers seem to be just the solution you need, there are a few issues you should keep in mind before opting for them.

Platform-Dependent

As their name goes, they are highly specific to the platform you’re using. In some cases, your deployment platform might not even offer such a service. Changing your deployment platform for getting access to such a service might not seem to be the best decision.

Non-Uniform

Since they are offered and managed completely by the deployment platform, such services can be highly non-uniform. Moving variables from one platform to another can be problematic. You can not even assume that every deployment platform would provide the options of importing/exporting environment variables. While most do, it is entirely in the hands of the platform. There are high chances of running into a small-scale vendor lock-in if you have a long list of environment variables.

No Local Support

While such services are great for accessing environment variables in your app’s deployments, there’s rarely a chance that you can use these while developing your app locally. In most cases, you’ll have to resort to managing local .env files. While it fulfills the purpose, it complicates the whole setup unnecessarily.

Using Secret Managers

The third option which is currently quite young in its development stages is to use dedicated secret managers. Secret managers are third-party services that enable you to isolate your app secrets completely from your source code/deployment and fetch them as and when needed over secure network connections.

Pros

Here are some of the advantages that secret managers offer over other methods of secret management.

Highly Secure

Since your secrets are stored in a completely isolated service, you can be assured that you’ll most likely never leak them out accidentally while sharing them with your colleagues or via version control commits. The third-party platform takes care of keeping your secrets safe, and they usually have quite strict SLAs when it comes to data security.

Even when accessing the secrets from inside of your application, most secret managers provide their own client code that can securely fetch and allow access to the secrets wherever needed.

Uniform Across Environments

Since the secrets are now independent of your codebase and deployment environments, you can now enjoy uniformity across environments. You do not need to make special arrangements for onboarding new developers or take special steps before pushing your app to production — most of these aspects are simplified or taken care of by your secrets manager.

Cons

While secret managers appear to be the best possible solution at hand for managing environment variables, they have their own share of caveats.

Cost

Managing environment variables has been an internal activity within projects for a long time. Even most deployment platforms provide this feature free of cost since they do not incur any additional costs for it.

However, since secret managers are completely independent services, they have their own cost of operations. Hence, users have to bear this cost while using these services.

Early Stages of Technology

Since the technology is fairly new, you can never be sure of how well it is going to be adopted by the industry in the coming days. While secret managers show great promise in terms of security and ease of management, the cost factor and data handling concerns might result in rather slow adoption of the technology.

How To Work With Environment Variables

Now that you understand the concept of environment variables and the available ways to implement them in an app, let’s look at how you can work with them via the terminal and in Node.js-based apps.

Environment Variables in the Terminal

Since environment variables are specific to processes, you can set and delete them via the terminal so that they are passed down to the processes that your terminal spawns.

Finding Variable Values

To view the list of environment variables in your terminal, you can run the following commands specific to your operating system.

On Windows:

set

On Linux or MacOS:

env

This will print a list of all available environment variables to your running terminal.

Setting New Variables

To set new variables via the terminal, you can run the following commands.

On Windows:

set "VAR_NAME=var_value"

On Linux or MacOS:

VAR_NAME=var_value

Deleting Variables

To delete an environment variable, you need to run the following commands.

On Windows:

set "VAR_NAME="

On Linux or MacOS:

unset VAR_NAME

Please note that the methods discussed above are only for creating/deleting environment variables for the current session of the terminal.

Environment Variables in Node.js

JavaScript is one of the most popular programming languages right now. It is widely used in building backend as well as front-end applications, making it one of the most versatile programming languages.

Node.js is among the most widely used JS frameworks for building backend applications. Let’s take a look at how you can handle environment variables in Node.js-based apps easily.

Accessing Environment Variables Directly

Node.js provides you with a reference to your current process’ environment variables via process.env. You can view the available environment variables by printing this object to the console.

Node.js app's output on the terminal showing a list of available environment variables.
Printing the process.env object.

This object will contain variables available to the running Node process. You can add new variables to it by declaring them before running your app, similar to this:

VAR_UNO=SOMETHING node index.js
Node.js app's output on the terminal showing a list of available environment variables along with the new variable added on the top.
Printing the process.env object after adding a new variable.

As you can see, the new variable gets added to your process.env object. However, you can not access any variables defined under a .env file via this method. To do that, you need to use a package like dotenv to load the .env file on runtime.

Using dotenv Package to Access .env Files

The dotenv package helps you to load environment variables stored in .env files at the root of your project. Its usage is simple, you need to install it by running the following command:

npm i [email protected]

Next, you need to add the following line of code in the beginning of your app’s code to initialize the package:

require('dotenv').config()

That’s it! Now the secrets that you store in a .env file at the root of your project will be loaded in your process.env object as soon as the app starts. We will see this method in action later in the following tutorial.

Environment Variable Tutorial

The best way to understand a technical concept is to watch it in action. Here’s a quick tutorial to help you get started with environment variables and learn their usage in detail.

In the following guide, we will demonstrate how to use environment variables via all of the three ways that we mentioned above—.env files, platform-native variable managers, and secret managers. All of these ways will require a common step, which is to set up a basic Node.js project.

First: Create a Node.js Project

To begin, ensure that you have Node.js installed on your system. Once you have it set up on your system, you’ll have access to npm (short for Node Package Manager). npm helps you to install node packages from the global npm registry via the command line. It will come in handy when installing packages related to our test project.

Next, open up a terminal and create a new directory. Initialize a fresh Node project in it:

mkdir my-app
cd my-app
npm init

Keep pressing enter through the incoming questions to accept the default options. Once done, your terminal would look something like this:

Terminal output showing the steps leading to the initialization of a fresh node app.
Creating a new project.

Now you can open up this project using an IDE such as Visual Studio Code or IntelliJ IDEA.

Create a new file in the root of your project folder and save it with the name index.js. This will be the starting point for your application. Next, install Express for quickly creating and testing REST servers:

npm i express

Once you have Express installed, paste the following piece of code in your index.js file:

const express = require("express")

const app = express()

app.get("/", (req, res) => {
   res.send("Hello world!")
})

app.listen(8080);

This is a starter snippet for a basic “Hello World” endpoint using Node.js and Express. Run the following on your command line:

node index.js

This will start your node + express app. If you navigate to http://localhost:8080 on your web browser, you’ll receive a similar output:

The text "Hello world!" printed on a blank HTML page.
Printing Hello World!.

This indicates that you’ve set up your app properly! The next step is to update your app to use environment variables. Update the code in index.js to match the following snippet:

const express = require("express")

const app = express()

app.get("/", (req, res) => {
  
   // the responseMessage object extracts its values from environment variables
   // If a value is not found, it instead stores the string "not found"
   const responseMessage = {
       environment: process.env.environment || "Not found",
       apiBaseUrl: process.env.apiBaseUrl || "Not found"
   }

   res.send(responseMessage)
})

app.listen(8080);

Instead of sending a “Hello world!” message in the response, we will now be sending a JSON object that carries two pieces of information:

  • environment: Indicates the current environment in which the app is deployed
  • apiBaseUrl: Carries the base URL for a hypothetical API. We will change the value of this URL based on the environment that the app is deployed in.

In case of not being able to access the environment variables, the object will contain “Not found” as the values for the two keys described above. Before moving ahead, rerun the node index.js command and you’ll receive the following output:

A JSON object with two keys environment and apiBaseUrl with values "Not found" in each printed on a blank HTML page.
Printing the default values for the env variables.

This indicates that your app is currently unable to access the environment variables. On top of that, we have not even defined the values for these variables. Let’s take a look at the various ways available to do that in the next sections.

Type 1: Add Environment Variables Via env Files

Let’s begin with the most basic way of adding environment variables — env files. In your project root, create a new file called .env and store the following code in it:

environment=DEV
apiBaseUrl=http://dev.myApi.com:8080/v1

Next, restart your app by running the node index.js command again and check for the output:

A JSON object with two keys environment and apiBaseUrl with values "Not found" in each printed on a blank HTML page.
Printing the values for the env variables.

You will find that the output still remains the same. This is because even though you’ve defined the value of the environment variables, you’ve not really instructed your app on where to find them. This is where packages such as dotenv come handy in.

Run the following command to install dotenv:

npm i [email protected]

To begin using dotenv in your code, add the following code on line number 2 of your index.js:

require('dotenv').config()

Next, restart the app and check for the output:

A JSON object with two keys environment and apiBaseUrl with values DEV and http://dev.myApi.com:8080/v1 in each printed on a blank HTML page.
Printing the new values for the env variables.

As you can see, the values for the environment variables have been loaded from your .env file!

Now, to spice things up, let’s rename our .env file to .env.dev and create another file by the name .env.staging. Paste the following piece of code into the new file:

environment=STAGING
apiBaseUrl=http://staging.myApi.com:3000/v1

Once you’re done with, replace line number 2 of your index.js file with the following piece of code:

require('dotenv').config({
   path: "STAGING" === process.env.NODE_ENV?.toUpperCase() ? './.env.staging' : './.env.dev'
})

console.log(process.env.NODE_ENV)

What’s changed here is that we’re now instructing the dotenv package to fetch the contents of the environment variables from one of the two available files based on another environment variable called NODE_ENV. Where does the value of this environment variable come from? Your terminal.

To test out this setup, run your app using the following command:

NODE_ENV=DEV node index.js

If you go to localhost:8080 now, you’ll notice the following response:

A JSON object with two keys environment and apiBaseUrl with values DEV and http://dev.myApi.com:8080/v1 in each printed on a blank HTML page.
Printing the env values for dev environment.

Now, kill the running app and run it again using the following command:

NODE_ENV=STAGING node index.js

Going to localhost:8080 will now lead you to the following response:

A JSON object with keys environment and apiBaseUrl and values STAGING and http://staging.myApi.com:3000/v1 in each printed on a blank HTML page.
Printing the env values for staging environment.

This is how you can use .env files to access different sets of variables based on external conditions. The external condition here is the external environment variable NODE_ENV which is essentially a user environment variable while environment and apiBaseUrl were runtime environment variables. The calling user profile supplies the value of the NODE_ENV variable and the app utilizes it to make internal decisions.

In case you are on Windows, you might face difficulties while running the previous two commands. The Windows terminal might not allow you to assign user environment variables on the fly using the KEY=VALUE syntax (unless it is Bash on Windows).

A quick solution in that case is to make use of scripts and cross-env.

Install cross-env by running the following command:

npm i --save-dev cross-env

Next, go to your package.json file and update the scripts key to match this:

// …
"scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "start-dev": "cross-env NODE_ENV=DEV node index.js",
   "start-staging": "cross-env NODE_ENV=STAGING node index.js"
 },
// …

Now you can run the following commands to run your app with two different sets of environment variables:

npm run start-dev		# to start with dev variables
npm run start-staging		# to start with staging variables

This is a universal fix and you can use these scripts on bash/zsh as well.

Next, let’s take a look at how to use a third-party variable manager.

Type 2: Use Heroku’s Native Environment Variable Manager

To be able to follow along during this part of the tutorial, you’ll need a Heroku account. Go ahead and create it before moving ahead.

Before deploying the app on Heroku, there are a few changes that you’ll need to make to your code. First of all, add a new script in your package.json called start:

"scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "start": "node index.js",
   "start-dev": "cross-env NODE_ENV=DEV node index.js",
   "start-staging": "cross-env NODE_ENV=STAGING node index.js"
 },

This script will be used by Heroku to run your app once deployed. Also, go to the last line of your index.js file and update it to the following:

app.listen(process.env.PORT || 3000);

This will ensure that the app is deployed on the port specified by Heroku.

Next, you need to install the Heroku CLI locally to be able to access your Heroku apps from your command line. For this tutorial, you can use the following command to install the CLI:

npm i -g heroku

However, Heroku docs recommend installing it via one of the other ways listed in the link mentioned above for better support. Once installed, run the following command to log in to your Heroku account:

heroku login

Once done there, go to dashboard.heroku.com and log into your account. Next, create a new app by clicking on New > Create new app.

Heroku's dashboard with the new dropdown open showing an option to create a new app.
Creating a new app.

On the next page, click on the Create App button without entering any details. This will create a new heroku app for you with a random name.

A form with a text input to enter the new app's name and choose a region.
Naming your app.

Here’s what your app’s dashboard would look like:

The new app's homepage with instructions to deploy for the first time.
The new app’s dashboard.

This page also contains instructions on how to deploy your app to Heroku via the CLI. Here is the list of commands you have to run to deploy your app to Heroku:

git init
heroku git:remote -a whispering-shelf-49396 # change whispering-shelf-49396 with the name of your app. You can get it in your dashboard
git add index.js package-lock.json package.json # do not push the .env files since we'll provide that via the in-app secrets manager git commit -am "Initial commit" git push heroku main 

Once the deploy is completed successfully, you can view the deployed app via the dashboard by clicking on the Open App button

If you’ve done everything right, you’ll see the following response:

A JSON object with keys environment and apiBaseUrl and values "Not found" in each printed on a blank HTML page.
Accessing your deployed app.

Why does this happen, you may think? This is because we haven’t provided the environment variables to our app in any form yet. If you check the commands that you used to deploy the app, you’ll notice that we haven’t pushed the .env files to our version control. We have also not defined the NODE_ENV value in the start script.

To inject environment variables into your app now, you need to navigate to Settings > Config Vars on your Heroku Dashboard. Upon clicking Reveal Config Vars, you’ll see a similar screen:

The new app's settings page showing app information and an empty list of configuration variables.
Accessing your app’s config vars.

You can now enter the contents of your .env file here:

The new app's settings page showing app information and a populated list of configuration variables.
Entering your environment variables in Heroku.

If you go back and refresh your app’s URL, you’ll notice that the values have been updated:

A JSON object with keys environment and apiBaseUrl and values PRODUCTION and http://prod.myApi.com/v1 in each printed on a blank HTML page.
The app deployed on Heroku can access environment variables now.

This is how you can make use of a deployment platform’s secrets manager to manage your environment variables. However, this requires you to manually manage the environment variables via the platform’s dashboard. As you can see for Heroku, there is no option to import a large dump of environment variables from a file, forcing you to enter each of them one by one. This can get quite troublesome at times.

The next section features a more efficient method of managing app secrets — secret managers.

Type 3: Use Doppler to Manage Environment Variables

The methods discussed above do not provide uniformity across platforms. You would usually not rely on env files when deploying to production, and it is not possible to make use of platform-native variable managers when working locally. Secret managers like Doppler fill this gap.

To try it out for yourself, you need to first create a free account on Doppler.

Once done, you can follow along. It will most probably create a sample project for you automatically by the name example-project. For simplicity, let’s keep it aside and create a fresh project for our use case.

Go to the dashboard and click on the plus icon next to Projects.

Doppler's projects section showing one project named example-project.
Your projects in Doppler.

Enter its name as “my-app” and proceed. Here’s what the project should look like once it is ready:

A set of environments with empty lists of variables on the app dashboard in Doppler.
Your project’s environments and variables.

Click on the dev config list item. Click on Add New Secret and add two secrets as follows:

A populated list of environment variables under the dev environment on Doppler
Adding the dev variables to Doppler.

Click on the Save button to save your changes. Note that Doppler does not support camel casing when naming your secrets. While it is generally preferred to keep the secret names in all caps, using Doppler does not leave you with any other options. To identify the secrets being fetched from Doppler, we’ve prefixed them with DP_

Now that the variables are created and stored in Doppler, let’s integrate them into our codebase. You need to set up the Doppler CLI to do so. Here’s a quick gist of what you need to do.

First of all, install the CLI locally by following the instructions specific to your operating system. Next, login into the Doppler CLI with your newly created account using the following command:

doppler login

Once logged in, run the following command to connect to your remote Doppler project from your CLI:

doppler setup

Once you’ve connected to the right project, you’ll be able to access your secrets via the following command:

doppler run

However, we will not run this command directly. Instead, we will append this to one of our run scripts so that it gets run automatically whenever the app starts. Let’s update the package.json file with the new run script for the dev environment:

// …
"scripts": {
   "test": "echo \"Error: no test specified\" && exit 1",
   "start": "node index.js",
   "start-dev": "doppler run -- node index.js", // This has to be updated
   "start-staging": "cross-env NODE_ENV=STAGING node index.js"
 },
// …

Also, remember that we created new variable names in Doppler that started with DP_. So we’ll need to update our index.js file to display this variable as well:

const express = require("express")
require('dotenv').config({
   path: "STAGING" === process.env.NODE_ENV?.toUpperCase() ? './.env.staging' : './.env.dev'
})

console.log(process.env.NODE_ENV)

const app = express()

app.get("/", (req, res) => {
  
   // the responseMessage object extracts its values from environment variables
   // If a value is not found, it instead stores the string "not found"
   const responseMessage = {
       environment: process.env.environment || "Not found",
       apiBaseUrl: process.env.apiBaseUrl || "Not found",
       DP_ENVIRONMENT: process.env.DP_ENVIRONMENT || "Not found" // Add the new variable here
   }

   res.send(responseMessage)
})

app.listen(process.env.PORT || 3000);

To see things in action, run the following command:

npm run start-dev

This is what http://localhost:3000 should look like now:

A JSON object with keys environment, apiBaseUrl, and DP_ENVIRONMENT and values DEV, http://dev.myApi.com:8080/v1, and DOPPLER_DEV in each printed on a blank HTML page.
Accessing Doppler secrets in an app running locally.

The third variable in the list (DP_ENVIRONMENT) is being fetched from Doppler directly.

Next, you can connect Doppler with your deployment platform to access these secrets via your deployed app. To do that, let’s begin by creating a new environment in Doppler for a new set of deployment secrets.

Go back to the home page for your project my-app. Click on the only list item in the Staging list:

Lists of environment variables under the my-app project with two variables defined under the development environment.
Your app’s dashboard.

You will notice that the two secret variables that you’ve defined in the dev environment are already available here but they’re missing values:

Lists of environment variables under the staging environment with missing values.
Your app’s environment variables for the staging environment.

Add the following values and click on Save:

Populated list of environment variables under the staging environment of your app.
Your app’s environment variables for the staging environment.

Once done here, click on the Integrations tab and Add Sync on this page to begin connecting to your deployment platform. You will receive a list of platforms that Doppler can integrate with:

List of deployment platforms that Doppler can integrate with.
Connecting your Doppler app to Heroku.

Since our app is deployed on Heroku, let’s click on Heroku. Follow the on-screen steps to connect your Heroku account to Doppler and provide the required access roles. Once it’s connected, you’ll reach a similar screen:

Heroku integration flow with a form on right that asks for project and config details.
Provide your app details to integrate with Heroku.

Select the Project Type as App, choose your Heroku app in the list of available apps, choose stg as the Config to sync, and Do Not Import in Import Options, since we do not want to import any secrets from Heroku to Doppler.

Click on Set Up Integration when done. You will reach a similar screen when the integration is made successfully:

List of integrated apps in Heroku, with the connect name, environment, destination URL, and status.
Your Doppler app’s integrations with Heroku.

Now if you check the Config Vars section in your Heroku app’s dashboard, you’ll notice that the variables from Doppler have been added automatically to your Heroku deployment’s config vars:

Populated list of environment variables in the Config Vars section of your Heroku app.
Doppler’s secrets are now synced with Heroku.

You’ll also notice that the two original environment variables (environment and apiBaseUrl) have been removed. This happened because we chose Do Not Import under Import Options. You can go ahead and add these again in Heroku if needed.

While the presence of the new environment variables in Heroku’s Config Vars section demonstrates that you’ve successfully set up Doppler to manage secrets both locally in your development environment and in your deployment environment as well, you can go ahead and deploy the updated code to Heroku to view the changes in the deployed app. Here’s what it’ll look like when done:

A JSON object with keys environment, apiBaseUrl, and DP_ENVIRONMENT and values STAGING, http://staging.myApi.com:3000/v1, and DOPPLER_STAGING in each printed on a blank HTML page.
Accessing Doppler secrets in an app deployed on Heroku.

This completes the tutorial to set up environment variables in a Node.js project using three popular methods. Next, let’s take a look at some ways in which you can ensure that your environment variables and their files are secure while they’re in use.

How To Keep Environment Variable Files Secure

While environment variables are a useful resource in modern DevOps practices, you need to be aware of the security implications that they can cause. Here are some tips you can use to keep your environment variable files secure and away from prying eyes.

Keep env Files Out of Version Control

One of the most important things you should keep in mind when handling any secrets is to keep them out of version control. Version control is meant solely for tracking changes across your application’s source code. Everything that goes in a version control system stays in it until deleted explicitly, and most of your team has access to this historical data for reference purposes.

If you’ve stored the keys to your AWS storage bucket or a paid API service in an env file for use in your application, you do not want to share it with your entire development team unless they are required to have access to it. If your project is open-sourced on a platform like GitHub, adding env files to your VCS could mean sharing it with the whole world! Env files are intended to be stored locally. You can provide each deployment environment with relevant env files via dedicated methods.

Always add the env file to your .gitignore file (assuming you use git for version control) or employ any other way to have your VCS skip the env files when committing changes. You can consider adding a template env file to your VCS so that any other team member may use it as a reference to create their env files locally.

Check Package Name Before Installation

Since you’d usually install most packages from NPM when working with a Node.js application, you should take extra care while doing so. It is well known that anybody can create and deploy an NPM package. And it’s also unsurprising that people often make mistakes when typing the name of a package that they want to install.

Multiple cases have been noted where malicious packages with names similar to some popular packages have been accidentally installed by users due to typing errors. Such packages are designed to gain access to your app’s environment variables and send them via the internet to their creators.

The only way to save yourself is to be alert whenever installing new packages from the internet.

Prefer Secret Managers Over env Files

With issues like accidental leakage while sharing and formatting errors, env files certainly aren’t the best option available for secret management. Enter secret managers. Secret managers such as Doppler enable you to isolate your app secrets completely from your source code and manage them in a dedicated platform.

You can grant your team access to these secrets directly on the platform, and your app can access these via encrypted connections over the internet. It solves all the woes associated with env files while allowing you the flexibility to maintain sets of secrets based on the environments of your application.

However, there are caveats here too. Secret managers are in a very early stage of their technological development. On top of that, secret managers are third-party platforms that are subject to their own set of security issues. Therefore careful evaluation and selection of the right secret manager are important. Moreover, if your application, its development team, or the number of app’s secrets isn’t large enough, secret managers might be overkill for you.

Environment Variable Reference Table

Here’s a quick cheat sheet to help you get up to speed with environment variables and their usage quickly:

Definition of environment variables Variables supplied to processes by their calling processes
Purpose
  • Store and secure app secrets
  • Manage environment-specific configuration data
Types For Windows only:
  • System
  • User
  • Runtime/Process
Popular example use cases
  • Private Keys
  • Environment Names
  • API base URLs, etc
How to implement these in your app?
  • .env files
  • Platform-native secret managers
  • Dedicated secrets management service

Summary

Environment variables are important for isolating sensitive data from your application. They help to secure your app’s secrets as well as enable you to switch between sets of secrets easily depending on the app’s environment. However, managing them adds another task to your plate.

There are multiple ways to secure and environment variables, as we discussed above. Explore them all and find which ones will fit and speed up your project the best.