Martin Lupa
Martin Lupa

Reputation: 89

Unique .env file in monorepo

I am trying to get to an understanding of what the best approach is to handling environment variables in a monorepo. I am using turborepo and the project structure is organised into a /apps and /packages directories among others.

The current state of the project is with app/package scoped .env and .env.local files. The file containing real secrets is the .env.local, which is only kept locally and shared among developers, while the .env is actually commited in the repo.

The idea is to only keep a root level .env as both turbo repo and the dotenv-cli libraries suggest and to simplify the setup, but I wanted to have some more ideas and references on what kind of things I should consider.

Some questions that I make myself around this are:

Thank you for your time.

Best regards,

I have moved all package/app scoped environment variables into a root level .env and .env.local files, but I would like to have some feedback in order to better decide.

Upvotes: 5

Views: 2248

Answers (2)

Pete - MSFT
Pete - MSFT

Reputation: 4309

I don't think it matters whether it's mono-repo or not. The same problem occurs for individual repos when you want to have shared settings across different applications. The slight advantage of separate repos is that it forces you to consider this problem earlier. But you still have to manage it.

I have a slightly different take to Matt wrt to committing env files. I'd specifically exclude .env.* files from your repo as a whole. People will still accidently include secrets, and unless you have checks, you'll end up rotating secrets sooner than you may want to. I do that with a .gitignore rule for .env wildcard exclusions to also cater for .env.dev .env.local .env.test anything that might be picked up as a convention from any one of your packages / apps / frameworks.

Then I would add a rule to that allows you to have .env-template or .env.template which contains the environment variable names and placeholders where appropriate. To create a build, you take a copy of the .env.template and make it your env local and fill in the gaps. It's self-documenting.

Finally, as part of your CI/CD system, you can populate the .env file as and how you need to from a keystore.

I like the idea of scoped env files at package/app level also. Line this up with a script (that's also committed) that can combine them at build time and both developers and build system can use it. That's speeds time to deploy.

Ideally - you'd want a separate place to grab keys/secrets/settings that isn't git, but that's a little out of scope for this question.

In the end, there's trade-offs. The amount of investment in getting it "right" compared to how often you blow away local dev environments and need to redo it. Solve for the most common case first, and then take it from there. For me, the biggest key is being able to recreate your environment quickly. That means scripting all set up, including your specific environment settings.

Upvotes: 1

Matt Kornfield
Matt Kornfield

Reputation: 356

I can share what we use where I currently work and what I think works best:

  1. For .env files that have environment variables that are OK to commit, non secret, direnv is great, as it hooks into your shell and can be path dependent, allowing you to have different .envrc files picked up depending on your path [1]
  2. For secrets, there are many options, but I really like HTTP bound secrets to be in a ~/.netrc file since tools like requests.py use it automatically [2] OR in a file in the home directory that can be source/ symlinked. Something like how a ~/.kube/config file functions [3]
  3. I think preferring types of auth that rely on temporary credentials like AWS profiles are the gold standard for repo security, and those are things you can write helper scripts for, and refer to the profiles by env vars, but you ultimately log in out of band of any env var usage.

The main reason to keep secret things completely out of the repo is to reduce the chance for committing them accidentally.

To answer your questions directly:

Should I only use a .env file and get rid of the .env.local?

I would remove local credential files as even being part of the repo, which I think is the purpose of .env.local

Is it a good practice to commit a .env file? What if a developer adds a secret there? the repository is private, but overall a bad idea to commit any .env file?

Always a bad idea to commit .env files with secrets, because of atomic committing in git, you have to essentially delete the repository if you want to clear it from the history (in the case that the repo is ever public/ shared in some way)

I saw in a different project where both root level as well as project scoped .env.local files were kept(containing only project specific secrets) and the root level .env.local containing repository wide secrets. What do you think of this approach?

I like it, it's essentially how direnv functions

If the overall objective is to simplify the project setup and onboarding for developers, by only sharing one file, then why to keep project level .env files?

I guess monorepos can get complex, in our case we use different AWS profiles in different sections of the repo is why we have separate .envrc files

[1] https://direnv.net/

[2] https://www.gnu.org/software/inetutils/manual/html_node/The-_002enetrc-file.html

[3] https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/

[4] https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-using-profiles

Upvotes: 1

Related Questions