Skip to content

Less Env, More Config

Published: July 12, 2026

4 min read

Categories: #Web


Less Env, More Config

Recently, I was cleaning up configuration in one of my own apps and ran into one of those tiny engineering smells that doesn't look serious until you stare at it for a while.

Too many things had become environment variables.

Not broken. Not dangerous on day one. Just... noisy.

The thing I keep coming back to -

Environment variables are great for boot-time secrets and deployment invariants.
They are not a great home for every operational knob a product might need to change.

It starts... innocently. A value needs to change without touching code, so it goes into an env. Then another one. Then a provider toggle. Then a limit. And because env vars are easy, the answer keeps becoming "just make it an env" - until a deployment surface has quietly become a control panel.

Every knob ends up on the same path:

You want to...The answer
Change a user-facing limitUpdate an env, redeploy
Disable one providerEnv
Rotate one small workflow's valueEnv again
Know who changed it last timeGood luck, hope the dashboard has audit logs. 🙃

Where it gets messy

  • Visibility.
    A setting in the app layer can be shown, searched, validated, even given a small Admin UI. As an env var, its source of truth is split between a hosting dashboard, CI variables, .env.example, and someone's memory.

  • Deploy coupling.
    Operational values change because the product is changing, not because the release is. Restarting a process to tweak them is fine twice, then it's friction people work around.

  • Drift.
    Staging gets one value, production another, local has an old copy, and a feature works on one machine because a forgotten .env file still has something the platform does not.

Nothing explodes in a fun way. It just wastes time.


The better question

Not "can this be an env?" - most things can. The question is:

"Does this value bootstrap the app, or does it describe how the app should behave?"

If the app can't safely talk to its own storage without it, that's bootstrap. Everything past that line is behavior.

That split cleans things up fast:

KindWhere it belongs
Runtime wiringEnv vars
Root secrets / KEKsEnv vars or a managed secret store
App behavior settingsDatabase / platform settings table
Tenant-specific configDatabase, scoped by tenant/project
Sensitive app settingsDatabase, encrypted with key versioning
Values edited by adminsDatabase, with audit/history if important

Not because databases are magically cleaner - you can absolutely make a mess there too. But behavior settings act like app data, so they belong where the app can own validation, history, and permissions. The storage does not have to be fancy; a small settings table is enough for a lot of apps. The important part is not the tool, it's the boundary.

You might ask - Doesn't 12-Factor say config goes in the env?

It does - and Factor III defines config as everything that is likely to vary between deploys. A feature flag or a per-tenant limit doesn't vary between deploys; it varies between Tuesdays. The split above isn't breaking the rule, it's reading it fully.


But some of it is sensitive

That's the part that makes people reach for env vars again. Fair - but sensitive does not automatically mean "must be a separate env var".

Keep one master key - a KEK in the environment, store the sensitive settings encrypted in the database, decrypt only where needed. Each record tracks its key version, so rotation is just: add a new key, re-encrypt gradually. The environment still protects the thing that matters most, and there's exactly one value to guard, audit, and rotate - instead of twenty sensitive-ish knobs scattered across deployment config.


Final thought

  • If a value is needed to start the app, keep it close to the process.
  • If a value describes how the app behaves, store it where the app can own it.
  • If the only reason something is an env var is "because config goes in env", it probably deserves another look.

Darshan Pandya