Run in a container
A container has no keychain, so penv keeps no local copy and asks the server on every start, which changes what your image and your health checks need.
A container has no keychain. penv detects that, keeps no local copy of the environment, and asks the server every time it starts. Everything else on this page follows from that one fact.
Put penv in the image
FROM node:22-slim
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL https://penv.cloud/install | PENV_ALLOW_ROOT=1 sh
ENV PATH="/root/.penv/bin:$PATH"
WORKDIR /app
COPY . .
CMD ["penv", "run", "--", "node", "server.js"]PENV_ALLOW_ROOT=1 is there because a docker build step runs as root and the installer refuses
root without it.
The installer writes one static binary to $HOME/.penv/bin and edits no startup file, which is why
the ENV PATH line is there. PENV_INSTALL_DIR moves it somewhere else. The Linux build is static
musl, so the same binary runs on a glibc base image.
There is no official penv container image yet.
Give the container a credential
Pass a pck_ machine credential in PENV_TOKEN. It comes before every other credential source, and a
container has no keychain to hold one anyway.
docker run --rm -e PENV_TOKEN="$PENV_TOKEN" my-app:latestWhere the host can prove itself, the credential does not have to be long-lived. A pod exchanges its
service account token, and a machine on a plain VPS enrolls a key once. Both end with a credential in
PENV_TOKEN and neither leaves a key in your image.
| The host can | Do this |
|---|---|
| Present a platform token | Exchange it, then set PENV_TOKEN. See Kubernetes. |
| Prove nothing at all | Enroll a key. See a host that proves nothing. |
Never bake a credential or a .env into an image layer. A layer is readable by anyone who can pull the
image, and deleting the file in a later layer does not remove it from the earlier one. Pass the
credential at run time.
Every start needs the network
There is no local copy in a container, so a start with an unreachable server stops with exit code 5 and says the address could not be reached. It does not start your program on stale values.
Two things to do about it.
Point your readiness probe at your own application rather than at the container starting, so an orchestrator retries a start that could not reach penv. And keep the credential exchange in the same step that starts the process, so a credential that expired between the two never happens.
The fifteen minutes an exchanged credential lives bounds the exchange alone. penv run reads the
environment once and hands the values to your program, which then runs as long as it likes.
When the tool has to read a file
Some tools read a file and nothing else. penv pull writes a plain .env beside the schema at mode
0600, and it also adds the file to .gitignore.
penv pullPENV_TOKEN is already in the environment from the docker run -e above, so pull reads it there.
Running penv pull inside a docker build writes the values into an image layer. Run it in the
container at start time, or in a build step whose output is not the image. A dynamic value is skipped
by pull and named in its report, because it has no stored value to write.
Under a coding agent, penv pull refuses unless a person passes --i-am-human, because it writes
every value to disk.
Which shape to pick
| Your container | Shape |
|---|---|
| Runs a long-lived process | penv run as the command, credential in PENV_TOKEN. |
| Runs a tool that reads a file | penv pull at start time, then the tool. |
| Cannot reach penv at start | An export sync into the platform's store. See serverless. |
Do it in order
- Add the installer and the
PATHline to your Dockerfile, withPENV_ALLOW_ROOT=1on the installer because the build step runs as root. - Change the command to
penv run -- <your command>. - Give the host something to prove itself with, or issue a
pck_credential in the console. - Pass that credential as
PENV_TOKENat run time. Never put it in a layer. - Start the container and read stderr. Exit code 5 means penv could not reach the server or had no credential.
- Point the readiness probe at your application so a failed start is retried.