Docs
Guidesince cloud@2026-09-10

Kubernetes

A pod authenticates with the service account it runs as. Project a token for penv, then wrap your process with penv run.

A pod authenticates with the service account it runs as. The cluster signs a token that names that account, penv checks it against a trust you set up once, and no key lives in a manifest.

Two routes reach a pod. This page is the one where penv runs inside the container. The other route hands values to External Secrets Operator, which is its own page.

Set up the trust

Open Machine Identities then Connect a Platform and pick the entry that matches your cluster. Five entries exist because the issuer is discovered differently on each.

EntryWhere the issuer comes from
KubernetesYou paste the cluster issuer URL.
Amazon EKSYou paste the URL aws eks describe-cluster prints.
Google Kubernetes Enginepenv builds it from three values your Google Cloud console shows.
Azure Kubernetes ServiceYou paste the URL az aks show prints, trailing slash included.
Oracle Container Engine for Kubernetespenv builds it from the region and the discovery key, with the realm under Advanced.

Every entry also asks for the namespace and the service account. The subject is always system:serviceaccount:<namespace>:<service-account>, matched exactly. The audience is always your workspace id.

The cluster issuer must be reachable from the internet. penv fetches the issuer's discovery document when the trust is bound, so an issuer nobody outside the cluster can read is a trust that can never bind. Each managed cluster above publishes one.

Finding your issuer

ClusterCommand
Anykubectl get --raw /.well-known/openid-configuration
EKSaws eks describe-cluster --name $CLUSTER --query cluster.identity.oidc.issuer
AKSaz aks show -n $CLUSTER -g $RG --query oidcIssuerProfile.issuerUrl -o tsv

The issuer is compared byte for byte against the iss claim, so paste it whole. AKS publishes its issuer with a trailing slash, and that slash is part of the value.

GKE needs no command. Its issuer is https://container.googleapis.com/v1/projects/<project>/locations/<location>/clusters/<cluster>, and every GKE cluster serves it by default.

OKE asks for the region and the OpenIDConnectDiscoveryKey that oci ce cluster get prints, plus the realm under Advanced, which defaults to Commercial (OC1). Its discovery document is served from object storage rather than the API server, so it is public whether or not the cluster is. Only an enhanced, VCN-native cluster with discovery enabled publishes one.

Project a token into the pod

The token your cluster mounts by default is addressed to the cluster, so penv asks for one of its own through a projected volume.

deployment.yaml
volumes:
  - name: penv-token
    projected:
      sources:
        - serviceAccountToken:
            path: token
            # Ask for this audience and no other. A token requested for two audiences fails every exchange.
            audience: <ORG_ID>
            expirationSeconds: 3600

containers:
  - name: api
    volumeMounts:
      # Without this the volume above exists and no container can read it.
      - name: penv-token
        mountPath: /var/run/secrets/penv
        readOnly: true

On EKS, do not reuse AWS_WEB_IDENTITY_TOKEN_FILE. That token is addressed to sts.amazonaws.com and its audience cannot be changed, so a projected volume of your own is the only path. The same holds for AZURE_FEDERATED_TOKEN_FILE on AKS, whose audience is Microsoft's.

Exchange it and run

T=$(cat /var/run/secrets/penv/token)
C=$(curl -sS "https://penv.cloud/api/v1/auth/oidc" \
  -H 'content-type: application/json' \
  -d "{\"token\":\"$T\"}" | jq -r .credential)
PENV_TOKEN="$C" penv run -- node server.js

The credential lives fifteen minutes. Your process lives as long as it likes, because penv run reads the environment once and then hands the values to the child.

The console prints this snippet ending in penv pull instead. Use penv run where you can, because it puts the values in one child process and writes nothing to disk. Use penv pull where the tool reads a file and nothing else.

A pod has no keychain, so penv keeps no local copy and every start asks the server. See run in a container for what that means for your probes.

The other route

Where your cluster already runs External Secrets Operator, you do not need penv inside the container at all. Its webhook provider reads the whole environment from GET /api/v1/bulk-read-secrets and writes a Kubernetes Secret, and your pod reads that the way it reads any other one. penv ships no Kubernetes sync adapter of its own, because that operator already does the job.

Routepenv at runtimeRotation reaches the pod
penv run in the containerYes, one call per startOn the next start
External Secrets OperatorNoOn the operator's own refresh interval

Do it in order

  1. Read your cluster issuer with the command for your platform, or the discovery key on OKE.
  2. Open Machine Identities, then Connect a Platform, and pick your cluster's entry.
  3. Fill in what that entry asks for, along with the namespace and the service account, and note the workspace id it shows.
  4. Add the projected volume with that workspace id as the audience, and mount it read only.
  5. Install penv in the image and change the command to penv run.
  6. Roll the deployment and read the pod log. A 401 unauthorized means the trust does not name this service account.

External Secrets Operator