Penv Cloud
Start free
Docs
Conceptsince 1.0.0-alpha.3

Schema and types

Your committed .env.schema names every key your app needs. Values stay out of the repository, and penv gen writes typed code from the schema.

.env.schema
# @penv=acme/api-gateway

# The primary Postgres connection.
# @type=url
DATABASE_URL=

# @type=string(startsWith=sk_) @rotate=90d @hosts=api.stripe.com
STRIPE_SECRET_KEY=

# @type=enum(development,staging,production)
NODE_ENV=development

You commit .env.schema to your repository. It names every key your app needs and what a valid value looks like.

Schema and values

FileCommittedHolds
.env.schemaYesKey names, types, decorators, non-secret defaults
.penv/config.tomlYesSettings, including [schema] version and [targets.<name>]
.env, .env.local, .env.<env>, .env.<env>.localNoValues, layered in that order (local and cloud modes)
Penv CloudNot a fileValues you store with us, in cloud mode

penv check validates your values against the schema, and every other command refuses to run on a schema that does not parse.

Decorators

The decorator grammar is @env-spec: root decorators, item decorators, data types and functions.

penv acts onPurpose
@penv=[<provider>:]<org>/<project>Links the schema to a project; provider penv (Penv Cloud) is the default
@currentEnv=$KEYNames the key that selects the environment
@hosts=Seals the key to the hosts it may reach (sealed runs)
@assert(expression, "message")Checked by penv run (exit 3 before the child starts) and penv check
@rotate=<span>A rotation reminder that never blocks (rotation state)

Values compute: ${KEY}, filters such as urlencode and base64, if, match, fallback, random(length=N), url(), json(), hash(), and penv() to read another environment, project or org. penv warns unknown_decorator on a decorator it does not act on.

penv check notes penv-only features that varlock will not load. The .env.schema format documents every addition, inference and diagnostic.

Rotation state

@rotate is a reminder; nothing replaces a value automatically. The due date is the current version's date plus the interval, so only a new value restarts the clock.

Units: y, m (calendar month), w, d, h, min, s, largest first, as in 90d or 1y6m. In local mode, penv records write times in .penv/config.toml and penv check names due keys.

StateWhenConsole shows
FreshMore than seven days before the due dateRotates every 90d
Due soonInside the last seven daysDue in 3d
OverduePast the due dateOverdue 12d
UnrecognisedWe cannot parse @rotateNothing

A key you never wrote has no due date. We email overdue keys once a week to your workspace's security contact, naming addresses and never a value. Procedure: rotate a value.

Generated types

penv gen ts     # src/env.ts: one typed env for server and client code, plus a Standard Schema validator
penv gen py     # penv_env.py on the standard library, or pydantic when your project uses it
penv gen go     # also rust, php, java, csharp

penv gen writes code that ships with your repository, so nothing calls penv at runtime. A secret field prints [redacted] in every language.

BehaviorDetail
Output locationAsked once, stored in [targets.<name>] in .penv/config.toml. --out <PATH> sets it non-interactively. An old .penv/targets/<name>/ folder migrates on the next gen
Secrets in tsRead by computed name, so no bundler inlines them into client code. The file masks sensitive values in console and Response bodies
Keys with a defaultAn unset or empty value takes the default, so the field's type never includes undefined
Build configpenv never edits tsconfig.json or package.json. It prints the import line, using an existing paths alias when one reaches the file

Per-language detail: typed env for a language.

Next: local mode and cloud mode.