How penv works
You declare what your app needs in one committed file, and penv checks every value before it hands them to your process.
One safe place for the keys and settings your apps need. penv is one static binary. It checks every value against a small file you commit, then starts your program with those values in its environment.
The four moves
Declare. penv init reads the .env you already have and writes .env.schema beside it. That file carries the key names and their types, and says which ones are required. It carries a value only when the value is dull, such as 3000 or us-east-1.
Check. Every run validates the values against the schema before your program starts. The check happens inside the binary, so a Go service with no penv library still gets a refusal that names the key.
Store. penv push moves the values to penv.cloud and deletes .env. From then on the cloud holds them and your teammate's whole onboarding is penv run.
Hand over. penv run -- pnpm dev puts the values in the child process environment and nowhere else. Nothing is written to disk, and the parent shell never sees them.
The one file you commit
# @penv=acme/api-gateway @schema=1
# @type=url
DATABASE_URL=
# @type=string(startsWith=sk_) @rotate=90d
STRIPE_SECRET_KEY=
# @type=url @sensitive=false
NEXT_PUBLIC_APP_URL=http://localhost:3000
# @type=port
PORT=3000There is no config file and no plugin. The comment lines above a key are its decorators, and they follow the @env-spec vocabulary, so a varlock user reads them on sight. The schema and your types covers the grammar.
.env is a file you can throw away
Before you push, .env holds the values and penv reads it. After you push, the cloud holds them and .env is a view you can regenerate with penv pull. Losing the file costs you nothing.
penv push deletes .env once the values are in the cloud. Run penv check first if you want to see what will move.
What the child process gets
penv run hands the child every key the schema resolved, plus PENV_ENV set to the environment it read. When a coding agent is driving the session, penv also scrubs those values out of the child's output before you see it. Coding agents covers what changes and what does not.
A key that sits in .env and not in the schema is masked anyway, and penv check reports it as drift.
Nothing about how your app reads a value changes
Your code keeps reading process.env.DATABASE_URL, or whatever your language calls it. penv sets the same names your app already uses. If you want the compiler to know those names too, penv gen ts and penv gen py write a typed file for you.
Next: where a value lives.