James Quigley's Blog

Environment Variables

April 03, 2020

Inspiration

I was inspired to write the post by an acquaintance who is new to the software industry. They couldn’t find any good resources on environment variables that covered everything in one place. They wanted to know things like:

Hopefully this post can become a kind of one stop shop for an introduction into environment variables.


What are Environment Variables?

Environment variables, commonly referred to as env vars, are variables available to a process that are a part of the environment where the process runs. Lazy to define the word with the word, so let’s break it down a little bit more.

A variable represents a value that can change. An environment variable typically represents a value set from outside the application, and exists as a part of system that is running the application. Even if you’re not aware of it, every time you use your computer, environment variables are impacting the applications you run. The browser that you are reading this blog post on probably uses hundreds of them. Some common examples are:

  • PATH (When you open a terminal and type a command, where should it look for the executable you are trying to run)
  • HOME (The home directory for the current user)
  • ENVIRONMENT (commonly used to say either “development” or “production” to specify where the app is running.)

Env vars keys by convention are written in all capital snake case, meaning multiple words in the key are separated by underscores. The value is a string. E.g.

THIS_IS_A_VAR_NAME=foobar

Environment variables exist within your system whether or not you use them in your applications. If you open a terminal and type the following, you’ll see the content of the environment variable PATH.


Mac OS/Linux

echo $PATH

Windows

echo %PATH%

What kind of things should be in environment variables?

There are many environment variables that will be set automatically by the system, but you can also set environment variables yourself specific to your application. Commonly you would set things that change based on where the app is running. These might be things such as:

  • Database credentials
  • Whether your app is running in Dev/Prod
  • What port your web application should run on

Some environment variables are secret and sensitive, like your database credentials. Others, like the port or dev/prod flag are not.

How can I set environment variables?

Ah the tried and true answer: “It depends”

In Unix based systems, like Linux or Mac OS, environment variables can be set for every shell session, the current shell session only, or exclusively for a single command:

  • Can be set for the shell session automatically (.bashrc/.zshrc etc)
  • Can be set temporarily manually export MY_VAR=foo
  • Can be set for a single command MY_VAR=foo ls

On Windows things are a little bit different. You can set in the environment variables settings for either the whole system or just current user, or in the terminal, use the command SET MY_VAR=foo. It’s different again if you’re using Powershell.

It is important to note that processes inherit the environment variables of their parent processes. However if all you need is to set some environment variables for your application, a very common convention is to put key value pairs in a a file called .env (pronounced: “dot ee en vee”. Or at least that’s how I say it) that lives alongside your project files, but is not committed to git. Many frameworks will automatically read from this file if it exists and set the environment variables for your process. If you aren’t using a framework that does this, many languages have libraries (usually called ”dotenv” or something similar) that will do the same. Some teams will have multiple .env files with different content, and might prefix them with their intended use, like sample.env, test.env, or dev.env.

A common question is how to properly set environment variables when running in CI systems like GitHub Actions or CircleCI. These systems run your build/test/deploy steps in their own isolated, ephemeral (temporary) container, and therefore they might lack some of the same environment variables that you have on your local development environment, and have many extras that you don’t. You should read into the docs of your CI system to see how to properly set environment variables and figure out which ones are already made available to you.

How can I read environment variables?

Here’s a smattering of examples of how to read environment variables in a few programming languages:

JavaScript:

process.env.MY_VAR

Using JavaScript fun, to provide a default you can use

process.env.MY_VAR || "some default value"

Python:

import os
os.environ["MY_VAR"]

os.environ returns a dict. Python dicts raise a KeyError if you try to access a key that doesn’t exist, so it tends to be better to actually do something like os.environ.get("MY_VAR", "some default value") to prevent that situation.

Go

import "os"
os.Getenv("MY_VAR")

Unlike other languages, Go returns an empty string if the key doesn’t exist. Make sure to take that into consideration!

Java

System.getenv().get("MY_VAR")

getenv() returns a Map<String, String>, and therefore a get for a key that doesn’t exist returns null

Rust

env::var("MY_VAR")

Because it’s Rust, env::var returns Result that could contain an Ok variant or an Err variant. You’ll need to handle that with either an expect, unwrap, or is_err on the Result.

If the value doesn’t exist, languages do different things. Some return undefined (JavaScript). Others throw an error. Whatever language you are using, make sure you properly handle the case where the value does not exist!


Environment Variables and Secrets

Not all environment variables are secrets! Many of them are totally fine to share, post publicly etc. However, some of them inevitably will be sensitive. As soon as you introduce a database, the credentials to that database will likely be set as environment variables and need to be kept secret. However secrets don’t have to be environment variables. There are other options too:

You will need to find the option that best suits you and your organization while still maintaining security best practices.

Don’t share secrets in Slack. Please. Just don’t. Use one of the myriad of options for sharing things securely:

I’m sure there are a million other options I didn’t include. This is just a few one of the ones I’m familiar with.


What if I really want to commit my secrets to git?

I really don’t recommend it, but if you absolutely must, you should at least use git-crypt or something similar so the file is encrypted!

What if I really want to just put the secret in a Slack message?

It’s just so easy to quickly send it over right? And you’ll definitely delete the message right after the person has a chance to copy it, right? There’s no way anybody might somehow get a hold of that secret, and you totally trust Slack that your messages will never be lost or read by someone else!

BAD!! STOP IT!

Seriously people, don’t do this!! There are so many good ways to send things securely, so stop posting plain text secrets in Slack, no matter how badly you want to. No matter how much of a rush you’re in. No matter how lazy you are.

Right now go download Signal and make an account. Go generate a GPG key pair and post your public key so people can send you encrypted messages. Do something encrypted. Anything! Just please don’t post it in plain text in Slack.


Written by James Quigley, a DevOps Engineer, full-stack dev, and general tech nerd. Follow him on Twitter!