Env vars in a monorepo, without the .env sprawl.
Every package wants its own secrets. Every developer wants one command. One .envshed.json at the root, every workspace pulls the keys it needs, and per-user overrides stay local.
A monorepo is the moment dotenv stops scaling. Package A needs a Stripe key. Package B needs a Postgres URL. Package C needs both. The next person on the team copies three files from Slack, misses one, and the app crashes at startup with a cryptic undefined. Envshed was built so the monorepo structure you already have stays intact — and secrets stop being the weak link.
Where monorepo secrets go wrong
Three packages, three .env files, one person keeping them in sync
One engineer becomes the walking dependency. They leave on vacation and the new hire's setup breaks in a way nobody can reproduce.
A new package ships on Tuesday, its keys are lost by Friday
Nobody remembers which env vars belong to the package you added last sprint. The .env.example file drifts one value at a time.
Turbo caches a task with a missing env var
The cache treats the missing key as a valid build output. The bug survives a week because the CI run that should have failed was served from cache.
A config that understands your workspace shape
Envshed treats your monorepo as a first-class structure. One config file at the repo root declares every package's environment. envshed pull walks the config, fetches only the keys each workspace declared, and writes them where that workspace expects them. pnpm workspaces, Turborepo, Nx, Lerna — if your tool reads process.env, Envshed delivers the values.
Declare once, pull per workspace
One config file. Every package pulls what it needs.
// .envshed.json at the repo root
{
"org": "acme",
"project": "platform",
"defaultEnv": "development",
"workspaces": {
"apps/api": ["DATABASE_URL", "REDIS_URL"],
"apps/web": ["NEXT_PUBLIC_API_URL"],
"packages/auth": ["JWT_SECRET"]
}
}What this replaces
Workspace-scoped pulls
envshed pull --workspace apps/api only writes the keys that the API app declared. Frontend packages never see the Stripe secret.
Missing-key detection before startup
Envshed fails loud when a workspace declares a key that is not in the vault. Runtime undefined becomes a lint-time error.
Turbo-friendly by design
Values write to .env files on disk. Turborepo's env and globalEnv keys read them exactly like before. Cache hashing does not change.
What you get
- envshed init detects workspaces and suggests a config
- Per-workspace key scoping — no leaking STRIPE_SECRET_KEY into the frontend bundle
- Per-user overrides alongside shared values — your local DATABASE_URL stays local
- Works with pnpm workspaces, Turborepo, Nx, and Lerna
- Audit trail at the workspace level — know who pulled what, where
Monorepo-specific questions
Do I need a separate account per package?
No. One organization, one project, one environment. The config declares what each workspace reads. Packages are just groupings of keys inside the same vault.
Does it respect Turborepo's cache hashing?
Yes. Secrets write to .env files on disk. Turbo's env and globalEnv keys read them from there. Cache behavior matches what you had before Envshed.
Can different packages use different envs at the same time?
Yes. Run envshed pull --workspace apps/api --env staging and envshed pull --workspace apps/web --env development side by side.
What if my monorepo has 80 packages?
The config stays declarative and diffable. You can generate it from package.json files in CI if you want to keep it machine-maintained.
Stop diffing .env files across packages
One command at the root. Every workspace pulls exactly what it needs.
Start freePart of the Envshed.