Makefile and Dotenv
Tags: make, cli, dotenv, configuration
Published: 2020-06-09 15:57:01

---
title: Makefile and Dotenv
published: true
date: 2020-06-09 12:00:00 UTC
tags: make,cli,dotenv,configuration
canonical_url: https://lithic.tech/blog/2020-05/makefile-dot-env
---
In this series on Makefiles, we've covered [how to use wildcards](https://lithic.tech/blog/2020-05/makefile-wildcards) and [using application presets](https://lithic.tech/blog/2020-05/makefile-apps) rather than having to pass in values.

There's one more technique we use for controlling Makefiles:

## Dotenv (.env files)

We **love love love** using `.env` files to configure our applications. If you're not familiar with them, the gist is that they specify environment variable keys and values in a very simple file format. For example, to control the `LOG_LEVEL` var, you can use:

```
LOG_LEVEL=info
```

And then load that at application startup (most languages have some sort of `dotenv` package for this).

While our full configuration practices are a topic for another day, we have a few rules we use on all of our projects:

- All configuration through environment variables (do not load files, do not talk to external stores)
- Always use `.env` files.
- Never check in `.env` files.
- Only have one `.env` file (no cascading files based on environment).
- `.env` takes precedence over values in the environment.

Some of these decisions are no doubt controversial, but the goal is that `.env` files provide unambiguous, canonical storage of local environment variables, and non-local environments use whatever native mechanisms for injecting environment variables before starting the process (Heroku Config Vars, AWS Parameter Store hooked up to ECS, etc).

## Make and .env

This ".env is the canonical storage of local environment" is so easy and standard, we can even set up Make to use it!

We put this block at the top of Makefile- it looks for a .env file, and if present, it will set Make variables from it:

```makefile
ifneq (,$(wildcard ./.env))
    include .env
    export
endif
```

Where is this useful? Well, let's say you want to connect to your local application's database. We can put that database URL into the `.env` file:

```
DATABASE_URL=postgres://appuser:pass@localhost:13005/myapp
```

(We always run our databases through `docker-compose` and use different ports for different projects- you won't ever go back to `:5432` once you start doing it this way)

Now in our Makefile, we can set up a target to connect via `psql`. Note the `cmd-exists-%` target from [last week](https://lithic.tech/blog/2020-05/makefile-wildcards)

```makefile
ifneq (,$(wildcard ./.env))
    include .env
    export
endif

cmd-exists-%:
	@hash $(*) > /dev/null 2>&1 || \
		(echo "ERROR: '$(*)' must be installed and available on your PATH."; exit 1)

psql: cmd-exists-psql
	psql "${DATABASE_URL}"
```

Now run it from the command line:

```
$ make psql
 psql "postgres://appuser:pass@localhost:13005/myapp"
 psql (12.2, server 11.4 (Debian 11.4-1.pgdg90+1))
 Type "help" for help.
```

## Docker too!

One more nice thing for this super-flexible `.env` file! Docker and docker-compose can accept an `--env-file` param, and by keeping everything in a single `.env`, you can share all the same configuration between your local application, Make, and Docker build.

Here's an adjusted Makefile snippet, and an example Docker command:

```makefile
ifneq (,$(wildcard ./.env))
    include .env
    export
    ENV_FILE_PARAM = --env-file .env
endif

docker-server:
	docker run --rm -it -p $(PORT):$(PORT) $(ENV_FILE_PARAM)
```

The `$PORT`, of course, is defined in the `.env` file :)

## More next time

Later this week we'll talk about one more neat thing we do with Make to standardize even further. If you are writing any JavaScript in particular, it's life-changing.

Finally, we'll wrap up this series in one week by talking about *why* we are all-in on Make as a task runner (if we tried to sell you on it in the first post you probably would have ignored us).