Keep your code secure by using environment variables and env files
Securely load a file containing all of our app’s required, confidential data like passwords, tokens, etc
Your code needs to connect to a database. For this it needs a password to that database. How can you use this password in your app in such a way that you can both connect to the database and keep the password private?
This article shows you how to use an env file in your code. This file stores all of our confidential information. It can be loaded once so you have access to all of your private information like passwords anywhere in your app. In this article we’ll use Python to illustrate the workings of an env file but the principle applies for all programming languages.
At the end of this article you’ll:
- understand the advantages of using environment variables
- understand how environment variables work
- understand how we can load environment variables from an env file
Let’s code!
1. Why use environment variables?
There are two main reasons to use environment variable files:
1. safety
2. flexibility
Keep your credentials safe
The first reason is the most important by far: environment variables keep your credentials save. We want to prevent our code being riddled with passwords and other information that other shouldn’t know. Not only is it bad practice; it’s also very dangerous, especially if you upload your code to a public repository like GitHub. Then you just publicly present your credentials to anyone who comes across it!
Environment variables collect all of our confidential information in a single file. Since all of our private info is collected in a single file we can easily gitignore
this single file to prevent our data from being pushed to our git repository!
Flexibility
Environment files can be modified externally. If your database password changes we can just open the file containing all the environment variables,
change the database password and restart our app. This is much more convenient than searching for, and then rewriting the hardcoded passwords within the app.
In addition you can run a program (e.g. in Docker) and ‘feed’ it an env file (like in the article below). This makes it very easy to switch between an development and production database for example.
2. How environment variables work
Now that we’re convinced that using env vars is smart, let’s figure out how they work. Every operating system (Windows, Linux, Mac) creates a set of environment variables for each process it creates. An example of one of these processes can be a script we’re running.
We’ll start with reading these variables and then add some variables of our own. The examples below are written in Python because this is a very accessible language but please notice that env vars are not exclusive to Python: functionalities like below are present in every type of programming language.
Reading environment variables
Let’s first check out which variables already exist;import os
for key, value in os.environ.items():
print(key, value)
We access the env vars with os.environ. This returns a dictionary. Using the items() method we can loop through all the keys and values and print them. In my case this prints out a lot of information about my system:
- locations of my PROGRAMFILES, PATH and APPDATA
- VIRTUAL_ENV information
- my USERNAME and COMPUTERNAME
- etc
We can also read one specific variable, for example my COMPUTERNAME:import os
print(os.environ.get(“COMPUTERNAME”))
Creating an environment variable
Since os.environ
is a dictionary we can easily add new variables. First we’ll create a variable with the key `HELLO` and the value `WORLD`. Then we’ll retrieve the value againimport os# Create an env var
os.environ['HELLO'] = 'WORLD'# Retrieve the newly created var
print(os.environ.get("HELLO"))
This print out `WORLD`. Easy! Also try to read this variable from somewhere else in the app and notice that it’s accessible everywhere.
3. Loading env vars into the env from an env file
Now that we understand the environment and how to use environment variables it’s easy to see what the environment file does; it specifies some extra pairs of keys and values that we want to load into our environment. In this section we’ll create an env file and then load it into the environment in its entirety.
a. Creating the env file
We’ll first create a file called `.env` at this location of our app: /config/conf/.env
. The content of the env file looks like this:DB_TYPE = postgresql
DB_USER = postgres
DB_PASS = mikepw
DB_HOST = localhost
DB_NAME = my_webshop_db
b. Loading the env file
Next we need to load this file into our app environment. Most programming languages have packages of built-in functionalities to load these kinds of files.
Most convenient for Python is to install a package: pip install python-dotenv
. This package conveniently loads the values in the .env
file in to the Python Environment. Then simply call the load_dotenv
function and pass the location of the .env
file.
The location of the .env file → working with relative paths in Python
The load_dotenv
function needs an absolute path to the location of the .env
file. In this article we’ll just pass it hardcoded but once we deploy our code we could face a problem here: on my machine the env file is located at is c:/users/mike/envfileproject/config/conf/.env
but on your machine it might be c:/pythonstuff/envfileproject/config/conf/.env
. Check out the article below for an easy, reusable trick to solve the headaches that come with calculating the absolute path to our env file.
c. Loading the env file
We’ll use the python-dotenv
package and the ROOT_DIR
to load the .env file into the environment:if (os.environ.get("DB_TYPE") == None):
from dotenv import load_dotenv
from config.definitions import ROOT_DIR
load_dotenv(os.path.join(ROOT_DIR, 'config', 'conf', '.env'))
First we’ll check if one of our required variables (in this specific case DB_TYPE) is already loaded in the environment. This is the case when we pass the env file to this Python project externally, through Docker e.g. Check out the article below on how to work with env files in Docker.
If the DB_TYPE variable doesn’t exist in the environment yet we’re going to load it using the python-dotenv
package.
- import the load_dotenv function from dotenv (this is the python-dotenv package we’ve installed earlier)
- Import the ROOT_DIR from
/config/definitions.py
- Pass the path to the .env file to the load_dotenv function
- DONE!
Our env file is now loaded into Python. We can securely and conveniently access all the values by requesting the key like this:database_password = os.environ.get("DB_PASS")
For a full example on how to impolement an .env file in a Docker container check out the article below. It it we use an env file to pass data to a Postgres container:
Conclusion
In summary: environment files offer us security and flexibility by storing all of our confidential information in a single file that we can load in our code to get secure access to all of the required values.
I hope to have shed some light on the inner workings of environment variables and how to work with environment files. If you have suggestions or clarifications please comment so I can improve this article. In the meantime, check out my other articles on all kinds of programming-related topics like these:
- Getting started with Postgres in Docker
- Create a fast auto-documented, maintainable and easy-to-use Python API in 5 lines of code with FastAPI
- Python to SQL — UPSERT Safely, Easily and Fast
- Create and publish your own Python package
- Create Your Custom, private Python Package That You Can PIP Install From Your Git Repository
- Virtual environments for absolute beginners — what is it and how to create one (+ examples)
- Dramatically improve your database insert speed with a simple upgrade
Happy coding!
— Mike
P.S: like what I’m doing? Follow me!