Docs
Guidesince cloud@2026-09-10

GitLab CI

Your job proves who it is with a token GitLab signs, then runs one step with a credential that lasts fifteen minutes.

Your CI logs in like a person does, with no key to steal. GitLab signs a token that says which project and branch the job ran on. Penv Cloud checks it against a rule you wrote once and hands back a credential that lasts fifteen minutes.

Set up the trust once

Open Machine Identities then Connect a Platform and pick GitLab CI. The form asks for two things, and hides two more behind Advanced.

FieldWhat it isDefault
Project pathThe group and project from your project's URL, such as acme/apinone
BranchOnly jobs on this branch authenticatemain
GitLab hostSelf-managed installs use their own URLhttps://gitlab.com
Ref typeBranch or tagbranch

From those, the console builds the subject project_path:acme/api:ref_type:branch:ref:main and stores it. Nobody types that string by hand. It is compared exactly, with no wildcards, so a typo is a trust that binds and then never matches.

A self-managed GitLab has to be reachable from the internet, because Penv Cloud reads its signing keys from the issuer's own origin.

Run one step with the values

Declare the token in the job, then hand it to penv.

.gitlab-ci.yml
deploy:
  id_tokens:
    ID_TOKEN:
      aud: YOUR_WORKSPACE_ID
  script:
    - curl -fsSL https://penv.cloud/install | sh
    - export PATH="$HOME/.penv/bin:$PATH"
    - penv run -- npm run deploy

aud is your workspace id, which the console shows when it creates the trust.

Ask for this audience and no other. A token requested for two audiences fails every exchange.

Name the variable ID_TOKEN and penv finds it on its own. The binary looks for a credential in PENV_TOKEN, then the keychain, then an enrolled keypair, then a platform token, and a GitLab runner has none of the first three. It reads ID_TOKEN or CI_JOB_JWT_V2, exchanges it, and runs your command with the values in the child process.

If you would rather do the exchange yourself, or your variable is named something else:

.gitlab-ci.yml
deploy:
  id_tokens:
    PENV_ID_TOKEN:
      aud: YOUR_WORKSPACE_ID
  script:
    - |
      C=$(curl -sS "https://penv.cloud/api/v1/auth/oidc" \
        -H 'content-type: application/json' \
        -d "{\"token\":\"$PENV_ID_TOKEN\"}" | jq -r .credential)
      PENV_TOKEN="$C" penv run -- npm run deploy

PENV_TOKEN takes precedence over everything else, which is how a container hands penv a credential. A runner has no operating system keychain, so every run is online and nothing is cached on disk.

For a host that needs a file instead of a process, penv pull writes a plain .env in the build step.

What the trust does and does not hold

  • The trust stores no secret. It is a statement that a token from one issuer, addressed to your workspace, carrying one exact subject, is a given identity.
  • The audience is always your workspace id, so a token minted for another workspace cannot authenticate at yours.
  • The credential the exchange returns is bound to one project and one environment. A request naming any other environment is refused with 403 forbidden.
  • A minted credential never outlives the trust's own expiry, so fifteen minutes is a ceiling rather than a promise.

When it does not work

What you seeWhat it means
401 unauthorizedThe subject on the token is not the one the trust names. Check the branch and the project path
401 expiredThe trust's own expiry has passed. Reissue it in the console
503 unavailablePenv Cloud could not read your GitLab's signing keys. Retry once
403 forbiddenThe credential is bound to another environment

A job on a tag rather than a branch carries ref_type:tag, which is a different subject and needs its own trust. So does a second branch.

Do it in order

  1. Open Machine Identities then Connect a Platform and pick GitLab CI.
  2. Fill in the project path and the branch, then read back the subject the console shows.
  3. Copy the workspace id. It is the audience.
  4. Add the id_tokens block to the job, naming the variable ID_TOKEN.
  5. Add the install step, then penv run -- <your command>.
  6. Push and read the job log. A 401 unauthorized means the subject does not match.

Other CI platforms