Local mode and cloud mode
penv is in one of two states, decided by the schema header and your credential, and cloud mode keeps an encrypted copy on your machine so runs stay fast.
penv is always in one of two states, and it tells you which one when you type penv with no arguments.
The two states
| State | You are here when | Values come from |
|---|---|---|
| Local | .env.schema has no @penv= header, or you are not signed in | .env on your disk |
| Cloud | The header names a project and you hold a credential | penv.cloud, through an encrypted local copy |
penv push is the move from the first to the second. It sends the values, then deletes .env.
If a .env is still sitting beside a schema that names a cloud project, penv run uses the file and says so on stderr. Delete it once you have pushed, or regenerate it deliberately with penv pull.
What a cloud run actually does
find .env.schema, nearest upward under 1ms
open the encrypted local copy about 2ms
still fresh? -> start your program
ask the server whether anything changed about 40ms
changed -> fetch, rewrite the local copy
cannot reach the server -> see belowpenv aims to start your program in under 50 milliseconds on a warm local copy. The check against the server is a HEAD request carrying the tag from the last fetch (its ETag), so an unchanged environment costs one small round trip and puts no values on the wire.
How long a copy stays fresh
| Environment | Fresh for | What happens on every run |
|---|---|---|
development | 60 seconds | Within the window, penv starts your program without asking the server |
| Every other name | 0 seconds | penv asks the server whether anything changed |
The difference is deliberate. You restart a dev server dozens of times an hour and a minute of staleness costs nothing. A production run should not begin on a value someone revoked a minute ago.
Offline
| Environment | The server is unreachable |
|---|---|
development | penv uses the local copy and prints one line saying so, once a day |
| Every other name | penv refuses with exit code 5 |
Failing closed on staging and production is the point. Yesterday's copy of a rotated production credential is the wrong answer, so penv would rather stop and tell you than start your program with it.
Where the local copy lives, and what protects it
The copy is a sealed file under your platform's cache directory: %LOCALAPPDATA%\penv\cache on Windows, ~/Library/Caches/penv on macOS, and $XDG_CACHE_HOME/penv or ~/.cache/penv elsewhere.
It is encrypted (ChaCha20-Poly1305) under a 32-byte key that penv keeps in your operating system keychain. The sealed bytes are bound to the server, the environment and the credential that fetched them, so a file for development will not open as production and a file one person fetched will not open for another. A file that will not open reads as no copy at all, so rotating your login is never an error.
A host with no keychain
Containers and most servers have no keychain to hold the key. penv detects that, keeps no local copy at all, and asks the server on every run.
A container running penv run needs the network every time it starts. Build that into your health checks, or use penv pull in the build step for a platform that has to read a file.
What cloud mode buys you
Change it once. It lands everywhere your code runs. A value you set in the console reaches every laptop on its next run, and every pipeline and server the next time one starts. Nobody has to paste anything anywhere.
Next: coding agents.