Serverless functions
A function is your code from the first line, so values arrive through the platform's own store, and a sync writes them on Lambda, Cloud Run, Workers, Vercel and Deno Deploy.
A function is your code from the first line. Nothing of yours runs before it, so there is no process
for penv run to wrap and no build step guaranteed to have written a file. penv ships no library for
reading a value at boot either, because a network call on every cold start is the runtime read penv
avoids.
Values reach a function through the platform's own store, and the console writes them there for you.
What a sync writes
| Platform | What it writes | When the value goes live |
|---|---|---|
| AWS Lambda | The function's environment variables on $LATEST | On the next instance the platform provisions |
| Google Cloud Run | The container's environment in a new revision | When that revision takes traffic |
| Cloudflare Workers | The Worker's secret bindings | Immediately, because the write rolls a new script version |
| Vercel | The project's environment variables | On the next deploy |
| Deno Deploy | One app's variables, for the context you pick | On the next deploy |
| Deno Deploy layers | One layer's variables, read by every app that includes it | On the next deploy |
Vercel has a second path as well, because a Vercel build can run a command. See deploy to Vercel.
Connect the platform
Open Integrations in the console, pick the platform, and give it what it needs to write.
| Platform | What it authenticates with | What one connection covers |
|---|---|---|
| AWS Lambda | A role you grant penv, with an external id penv generates | One account, and function names under one prefix |
| Google Cloud Run | A service account key file | One project and one region |
| Cloudflare Workers | An API token with Workers Scripts Edit | One account |
| Vercel | An access token, plus a team id on a team account | The token's own scope |
| Deno Deploy | An access token from console.deno.com | The app named on the connection |
| Deno Deploy layers | An access token that can edit layers | Whichever layer a sync names |
Then map one penv environment onto one target: a Lambda function name, a Cloud Run service name, a Worker name, a Vercel project and deployment target, or a Deno Deploy context.
Run a dry run before you apply. It names the keys a real run would write and the keys it would delete, using names alone, so you see a prune before it happens rather than after.
What each platform makes you live with
AWS Lambda replaces the whole environment variable map on every write, so penv reads the current
configuration and writes the merged map back, pinned to the revision it read. A published version's
configuration is locked, so writes target $LATEST alone. The write is asynchronous, which is why the
value arrives on the next instance rather than the next request.
Importing from Lambda needs kms:Decrypt. A variable encrypted under a customer managed key cannot be
read at all, and penv reports that rather than treating it as empty.
Google Cloud Run works the same way with a revision in place of a version. penv reads the service
and writes the merged list back pinned to the etag that read returned, treating both 409 and 412
as a lost race and re-reading afterward. A variable that points at Secret Manager is listed and preserved byte for byte,
and never fetched, so a sync does not quietly flatten one into a literal.
Cloudflare Workers is export only, and not by choice. Cloudflare returns secret names and never their values, so there is nothing for an import to read.
Deno Deploy is export only for the same reason: every variable penv writes there is marked secret, and Deno omits the value of a secret variable from its response. Deno does not say whether a change reaches an isolate that is already running, so penv reports the write as live on the next deploy.
Where the record stops
Once a value sits in the platform's store, the platform serves it. Reads there are not penv reads, so they land no entry in your audit log.
| You get an entry for | You do not get an entry for |
|---|---|
| The sync that wrote the value, and what it changed | Your function reading the variable it was given |
| A reveal, a write and a delete in penv | A person reading the value in the platform's own console |
That is the trade for a boot with no network call in it. Where you need every read written down, run
the code somewhere you can wrap with penv run.
Dynamic values are skipped
A dynamic value has no stored value to copy, because it is minted on request. A sync skips it and names it in the run report. See dynamic values.
Deno Deploy proves who it is
Deno Deploy sits in both catalogs. The sync above writes into its variables, and a deployment can also mint its own token and authenticate as itself, which is the only function platform where that is true.
| Part | Value |
|---|---|
| Issuer | https://oidc.deno.com |
| Subject | deployment:<organization>/<app>/<context> |
| Audience | Your workspace id |
import { getIdToken } from "jsr:@deno/oidc";
const T = await getIdToken("<ORG_ID>");
const r = await fetch("https://penv.cloud/api/v1/auth/oidc", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ token: T }),
});
Deno.env.set("PENV_TOKEN", (await r.json()).credential);The issuer is shared by every Deno customer, so the organization slug in the subject is what scopes the trust to you.
Do it in order
- Open Integrations in the console and connect the platform.
- Give it the credential it asks for, and let the connection verify before you go on.
- Map one penv environment onto one function, service, Worker or Deno Deploy context.
- Choose the write policy. Prune deletes keys your penv environment does not have.
- Run a dry run and read the plan.
- Apply, then redeploy or restart so the platform picks the values up.