Docs
Guidesince 1.0.0-alpha.3

Upgrade from 0.16

penv 1.0 is a new binary with one committed schema, so this page names what each 0.16 part became and the one path that gets your values across.

penv 1.0 is one static binary with nothing under it. The 0.16 line was a Node launcher that downloaded an engine, kept a tree of value files, and loaded plugins. None of that survives, so this is a move rather than an upgrade.

The move is one plain file per environment out of 0.16, then one push per environment into penv.cloud. Read the table first so you know what you are giving up.

Do it in order

Get a plain file out of 0.16, one per environment. Run this with the version you have installed today.

penv generate --env development --out .env.development --allow-decrypt
penv generate --env production --out .env.production --allow-decrypt

--allow-decrypt is what writes a sealed value as plaintext. Without it, generate refuses the sealed ones and you get an incomplete file.

Move those files into a holding directory outside the repository, say ~/penv-move/. They are plaintext, and the next steps delete the tree they came from.

mkdir -p ~/penv-move
mv .env.development .env.production ~/penv-move/

Remove 0.16. Uninstall the global launcher, then delete the engine store it downloaded into.

npm rm -g @penvhq/launcher
rm -rf ~/.penv/engines ~/.penv/extensions

Install 1.0. The two installers always take the latest release.

curl -fsSL https://penv.cloud/install | sh

See install the CLI for Windows and for npm.

Write the schema. Put the development file back as .env, then run penv init in the project. It reads the file and writes .env.schema, inferring a type and a sensitivity per key. It never copies a value into the schema.

cp ~/penv-move/.env.development .env
penv init
penv check

Read the guesses and fix the ones it got wrong. See from a dotenv file.

Send the first environment up. The .env you copied in the last step is still there.

penv login
penv push

push creates the project with one environment, development, writes the @penv= header into .env.schema, sends the values, then deletes .env.

Create every other environment in the console. push creates development and nothing else, so production has to exist before you can push to it. Open the project and add each one. See projects and environments.

Push each remaining environment. Copy its file back to .env first, because push reads .env and deleted the last one.

cp ~/penv-move/.env.production .env
penv push --env production

Repeat that pair for every environment you saved.

Read the values in the console. Open each environment and check its keys are there. This is the last moment the plaintext copies are your only source.

Delete what 0.16 left in the repository. Remove these:

  • penv.config.ts and penv.schema.ts
  • the whole .penv directory
  • any @env or #env path mapping in tsconfig.json or package.json

Then delete ~/penv-move/.

The files generate --allow-decrypt writes hold every value in the clear. Keep them until every environment shows its values in the console, then delete the whole holding directory and check that none of them reached a commit. Anything sealed under a local key in 0.16 has no other way across: penv 1.0 cannot read the penv:1: envelope format.

What each name became

This is the one page that names the retired parts. Nothing below exists in 1.0.

Configuration

0.161.0
penv.config.ts and defineConfigOne committed .env.schema. There is no config file to import.
penv.schema.ts and .penv/env.tspenv gen ts writes the typed accessor from the schema.
The @env and #env aliasThe target decides the import line, remembered in .penv/targets/ts/target.toml.
.penv/state/records/Values live in .env in local mode and in penv.cloud in cloud mode.
.penv/state/manifest.jsonNo replacement. There is no engine to pin.
.penv/state/extensions/ and local-extensions.jsonNo replacement.
.penv/state/cutover.json and rollback/No replacement. penv init leaves .env where it is until push.
The four-scope cascadeOne environment per read: --env, then PENV_ENV, then development.
Providers and provider extensionsTwo modes built into the binary: local and cloud.
@penvhq/provider-penv-cloudNo replacement. Cloud mode is the binary.

Commands

0.161.0
penv initpenv init, which now writes .env.schema from your .env.
penv import <file>penv init.
penv generatepenv pull, which writes a plain .env from the cloud.
penv get <key>penv reveal, which needs a person's approval under a coding agent.
penv set <key> [value]penv set.
penv fillpenv set, one key at a time.
penv mv <from> <to>No replacement.
penv pullpenv pull, now reading from the cloud.
penv pushpenv push, now sending to the cloud and deleting .env.
penv rotate <key>penv set writes a new version. @rotate in the schema records the interval.
penv runpenv run.
penv remove <key>penv unset.
penv listpenv ls.
penv cleanupNo replacement. There is no rollback bundle to drop.
penv migrateNo replacement.
penv encrypt and penv decryptNo replacement. Values are sealed by the server.
penv key createNo replacement in the binary. Your own wrapping key is set in the console on Enterprise.
penv validatepenv check.
penv doctorpenv check, plus penv with no arguments, which prints the state and the next command.
penv watchNo replacement.
penv artifact buildNo replacement. penv pull writes a plain .env.
penv installNo replacement. The installer places the binary.
penv add <package>No replacement.
penv upgrade [version]penv upgrade replaces the binary from the latest release.

New in 1.0 and worth knowing about: penv login and penv logout for a device-code sign-in, penv machine enroll for a host that can prove nothing, penv gen for typed access, along with penv guard and penv hook for keeping a coding agent out of .env.

penv gen is a different command from 0.16's penv generate, and the similar name is the trap. gen writes typed access for your language. The old generate wrote a value file, and penv pull is what does that now.

The variables penv reads

0.161.0
PENV_ENVPENV_ENV. Local mode accepts development alone.
NODE_ENV as an environment fallbackNo replacement. PENV_ENV is the only variable that names one.
PENV_HOMEPENV_INSTALL_DIR, read by the installers. There is no engine store.
PENV_KEY_*No replacement. There is no local key.
PENV_DELIVERYNo replacement.
PENV_SNAPSHOTNo replacement.
PENV_RUNNo replacement.
PENV_SCHEMA_HARVESTNo replacement.
PENV_DEBUGNo replacement.
PENV_CLOUD_TOKENPENV_TOKEN, holding a pck_ machine credential.

The full 1.0 list is on the variables penv reads.

Encryption

0.16 sealed a value on your disk with AES-256-GCM under a 32-byte key you supplied, either from PENV_KEY_<ID> or from the operating system keychain, and wrote it as a one-line penv:1: envelope.

1.0 has no local envelope. A value is sealed by the server under a key of its own, and the copy penv keeps on your machine is sealed with ChaCha20-Poly1305 under a key in the keychain. See encryption in the cloud.

There is no converter between the two. The generate --allow-decrypt step above is the path across.

About the npm packages

The 0.16 packages are still published, and latest still points at 0.16.2 on 10 September 2026. Nothing is marked deprecated on npm.

TagVersion
latest0.16.2
next1.0.0-alpha.3

npm i -g @penvhq/cli installs 0.16.2 today, whose only executable is penv-engine, so you end up with no penv on your PATH at all. While 1.0 is in prerelease, ask for it by tag: npm i -g @penvhq/cli@next. The two installers at https://penv.cloud/install always take the latest release, so they need no tag.

In 0.16 the command named penv came from @penvhq/launcher, which is why that is the package the uninstall step names. @penvhq/provider-penv-cloud was never published, so there is nothing to remove for it.

Next: changelog.