4 ms·
Ignoring the point about managing secrets for a moment, and just speaking about "sharing/syncing .env files" — I would like to suggest that anyone that cares a
by derefr 2y ago
Ignoring the point about managing secrets for a moment, and just speaking about "sharing/syncing .env files" —
I would like to suggest that anyone that cares about this, first gets onto using direnv(1) — and getting their collaborators on projects to do the same — rather than relying on the `.env`-file support included in Python / Docker Compose / etc.
Why? Because of a specific function that direnv(1) injects into its script execution environment, called source_env_if_exists().
You can use this function to separate your env-var config into two "layers": a project-global env, and a user-specific local-worktree env. The global layer can just call source_env_if_exists at the end, pointing at the local layer.
(In my own projects, I usually name this local overlay env-file `.envrc.local`)
With this approach, rather that creating an `.envrc.template` and telling each dev that they need to copy it into place, you can instead create and commit the `.envrc` file itself, holding any public/static base configs; tell your devs to override and add env in `.envrc.local`; and add `.envrc.local` to your project's `.gitignore`, so that nobody will accidentally commit their env overrides.
This also has an interesting impact: direnv(1) normally de-authorizes the sourcing of the `.envrc` file whenever its content-hash changes — which is annoying when you're constantly tweaking local env-vars. But (perhaps surprisingly), direnv(1) does not recursively hash the script files depended on through source_env_if_exists() for purposes of calculating the overall authorization hash of the toplevel script — so changing `.envrc.local` in this setup, doesn't result in having to type `direnv allow`.
As another bonus, with the `.envrc.template` approach, as soon as you copy `.envrc.template` to `.envrc`, this base config is now no longer "managed" by the project, and so can age/break. Whereas, a repo base `.envrc` can be updated by the project maintainers, and the resulting derived env will change along with it! (But, of course, making any change to the project base `.envrc` will result in each developer needing to re-authorize the `.envrc` file with `direnv allow` — which is the correct security model, as you don't want maintainers of public projects being able to attack users who've `direnv allow`ed the `.envrc` of their repo, by silently changing the content of that `.envrc`.)
And as an interesting extension to this, you can add code after source_env_if_exists(). So you can:
• validate the env-var values the developer set in `.envrc.local`
• require that certain env-vars be set in `.envrc.local` — where a value not being set results in the `.envrc` script printing an error message and exiting. The dev will see this as soon as they `direnv allow` in the dir, and the message can guide them to configuring the env-vars. (You can even do this conditionally, where e.g. a connstring env-var must be set for a provider if-and-only-if that provider is enabled.)
• allow env-vars to be specified in various different styles, together as URLs or separately as host/username/password/etc, and then canonicalize / synthesize the env-vars your project actually wants, filling in the parts of a computed var the dev didn't specify with default values
• you can require the `.envrc.local` file export an ENVRC_ABI_VERSION var, and compare that to a constant burned into the `.envrc` file, and error on mismatch. Then, if the project's base `.envrc` gets updated in a way where it now looks for different env-vars than before, you can just bump the constant in `.envrc`. When devs re-authorize the new `.envrc` version, rather than just being confused why everything is now broken, the `direnv allow` command will error out, pointing the dev to a URL on the project wiki, where there's `.envrc.local` version-migration guidance (which tells them whatever semantic changes they need to make, and then ends by telling them the new value to update ENVRC_ABI_VERSION to.)
...and lots of other things that I can't even think of right now.
(Of course, a lot of these benefits could also come from using a fancy configuration library in your actual application. But if your app is intended to be configured in a very specific way in production — for example, if it's a k8s CRD controller, and expects its config as a YAML ConfigMap resource — then it wouldn't really make sense to have all this extra plumbing in your app just for development. And so it can make a lot of sense to instead handle env-config glue-logic like this purely at the dev-env level, outside of the software.)
- globular-toast 2y agoI've tried to convince my team to use direnv and they just won't. I don't get it. Is it because it's not easy for them to install?