Team workflows
Stop passing .env files around your team
We've done this ourselves more times than we'd like to admit: someone rotates the staging API key, pastes the new .envinto Slack, and for the next hour half the team is quietly running against the old one because they didn't see the message. Multiply that by three environments and a couple of new hires a quarter, and the file is only ever correct for as long as nobody touches the source of truth without telling you.
The file was never the source of truth
A .envfile is a snapshot, and it starts going stale the second you hand it to someone. On a team of two that's barely worth mentioning - you just shout across the room when something breaks. Once you're twenty people across three environments, it turns into a standing argument about whose copy is current, plus a genuine security problem: how many Slack threads and email attachments now have a database password sitting in them, forever, searchable by anyone who joins the workspace later?
We didn't want a better way to pass the file around. We wanted to stop caring about the file. So that's what Nona does: the values live in one place, and a .envfile is just a photograph of them you can take whenever you need one. It's out of date the second you take it - on purpose, because the thing it's a photograph of doesn't stop moving.
Export: get today's values as a real file
Local tooling, CI runners, and one-off scripts still want a plain .env, so entries export writes one:
Wrote 2 entries to .envCheckout__FreeShippingThreshold=25
FeatureFlags__DarkMode=trueTwo details in there came out of code review, not out of us getting it right the first time. We'd originally written every key out literally and wrapped every value in quotes - reasonable-looking, and wrong twice over. A hierarchical key like Checkout:FreeShippingThreshold has to become Checkout__FreeShippingThreshold, because :isn't legal in a POSIX environment variable name and most .env parsers just drop a line whose key has one in it. __isn't an arbitrary substitute either - it's what .NET's own configuration system already binds back to :, so a Nona-based service reading this file as real environment variables gets its hierarchy back without us doing anything special. And a value only gets quoted when it actually needs it; most lines come out as plain KEY=value, because that's the one format basically every parser out there agrees on.
Import: apply it back, one key at a time
The other direction is entries import, and we almost gave it a name with "bulk" in it before catching ourselves. Every line in the file gets applied the same way a single entries set would apply it - one call per key, not one call for the whole file:
Would set [staging] Checkout:FreeShippingThreshold=25
Would set [staging] FeatureFlags:DarkMode=true
Dry run: 2 entries would be set.Set [staging] Checkout:FreeShippingThreshold
Set [staging] FeatureFlags:DarkMode
Imported 2 entries into [staging].__ turns back into :here, the exact reverse of export, so the hierarchical key you started with is the one you get back. And because every line becomes its own write instead of landing as one blob on top of the environment, each key keeps its own version history and shows up as its own line in the audit log - exactly as if someone had typed it into the admin console by hand. Twelve lines in a file means twelve ordinary changes, each one you can look at and roll back on its own, not one mystery event called "bulk import" that nobody can pick apart afterward.
--dry-runis there because we don't trust ourselves to get every line right on the first try, and we don't expect you to either: it shows you exactly what would change, on every key, before any of it actually does.
Piping one straight into the other
Neither command actually needs a file. export writes to stdout unless you tell it otherwise, and import reads stdin the same way, so cloning or promoting an environment is one line:
nona entries export --project demo-app --environment production \
| nona entries import --project demo-app --environment stagingThere's no temp file sitting on someone's laptop in between, no snapshot waiting around to go stale before anyone remembers to apply it.
What actually changes for a big team
Export and import are the parts you'll actually type - the CI job that still wants a file, the one-time seed of a new environment. What actually changes how a big team feels about config is quieter than that: there's exactly one place the values live. When someone updates a key, through the CLI, the admin console, or the API, every other reader gets the new value the next time it asks. Immediately. Not next time someone remembers to re-share a file, not on the next deploy - immediately, because there was never a second copy of it to fall behind.
And if what you actually need to hand someone is one value, not a whole environment - a support engineer who needs a single flag, a contractor who shouldn't see the rest - that's what shareable parameter links are for instead: a link to one entry, not a file with everything else sitting next to it.
The code is all there
entries export, entries import, and the parser and formatter behind them are all in the CLI source now, tests included - the escaping rules above aren't just asserted in a blog post, they're pinned down in the same repo.