Docs menu

Environments

.md

Protect deploy secrets: environments with reviewers, branch rules, workflow pins, and dedicated runners, deployed with `oak ci deploy`.

An environment is a named place your CI deploys to โ€” production, staging, app-store โ€” with its own secrets and its own rules about who can deploy, from which branches, and on which machines. It's how you keep a signing key or a deploy credential away from the rest of your CI.

CI secrets are available to every workflow on every branch, so anyone who can push can read them. An environment's secrets are much harder to reach. A job receives them only when all of these hold:

  • the job declares the environment (environment: production),
  • someone allowed to deploy approved that exact commit and workflow,
  • the commit is the current head of a branch the environment allows,
  • and the job runs on a runner dedicated to the environment, which runs nothing else.

Environment workflows never run on a push or a merge. They run when someone deploys them.

Set one up#

Environments are configured by the repository's managers (organization owners and admins) in the repo's Settings โ†’ Environments, or over the API.

  1. Create it. A new environment is enabled, requires a reviewer, and allows deploying from main.

  2. Add its secrets and variables. Secret values are encrypted and never shown again; variables are plain values anyone with write access can read.

  3. Add reviewers โ€” the people who may approve a deploy. They need read access to the repo. (Or turn off Required reviewers to let anyone with write access deploy.)

  4. Dedicate a runner. Register a runner for it, ideally on a machine that holds nothing else, and bind it to the environment. A bound runner takes only that environment's jobs โ€” never ordinary CI โ€” so code from an arbitrary branch never runs where the secrets land.

  5. Point a workflow at it. Give the job environment: and a manual trigger:

    # .oak/workflows/release.yml
    on: [workflow_dispatch]
    jobs:
      release:
        runs-on: release-mac
        environment: production
        steps:
          - uses: actions/checkout@v4
          - run: ./scripts/release.sh
            env:
              SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
    

    An environment workflow has exactly one job. environment: also takes GitHub's mapping form, {name: production, url: https://example.com}. The name must be written out: it can't come from an expression.

  6. Deploy it (below).

Deploy#

oak ci deploy --environment production            # preview, confirm, deploy main's head
oak ci deploy -e production --workflow release --branch main --yes
oak ci deploy -e production --dry-run             # just show what would deploy

A deploy has two steps. First Oak shows what would run: the branch head, the workflow and its exact content hash, the environment's current policy version, its reviewers, and anything in the way. Then you approve exactly those values. If anything moves in between, the approval is refused: the branch gets a new commit, the workflow changes, or someone edits the environment. You re-review the new state; nothing runs that you didn't see.

An approval starts one run and is good for 30 minutes. The first runner bound to the environment that polls takes the job. Just before it hands the job over, Oak checks every rule again: environment enabled, same policy, head unchanged, approver still allowed, runner still bound. If anything failed, the run fails and nothing is sent.

The same deploy is available from Settings โ†’ Environments โ†’ Deploy, and over the API as a preview (GET โ€ฆ/release-preview) followed by an authorization (POST โ€ฆ/ci/release-authorizations) carrying the preview's values. An API key used to deploy must carry the explicit release scope.

Protection rules#

RuleDefaultWhat it does
EnabledonA disabled environment deploys nothing.
BranchesmainOnly the current head of one of these branches can be deployed.
Required reviewersonOnly listed reviewers can approve a deploy. Off: anyone with write access can. Removing the last reviewer doesn't open it up; with this on and nobody listed, nobody can deploy.
Workflow pinsnoneWith no pins, any workflow whose job declares the environment can deploy. Add pins to allow only those workflow files. A pin can also fix the file's exact content hash, so even a merged change to the workflow can't deploy until someone re-pins it.
RunnersnoneThe runners that take this environment's jobs. At least one must be online to approve a deploy.

Every change to an environment moves its policy version, which cancels any approval made against the old settings.

Platform administrators of the Oak instance can also approve deploys.

What the job sees#

  • Secrets: the environment's secrets, through ${{ secrets.NAME }} as usual, plus the job's own GITHUB_TOKEN. Your repo's ordinary CI secrets are not included, so an environment job only ever has what you put in the environment.
  • Variables: as environment variables, and as ${{ vars.NAME }}.
  • Who and what: besides the usual OAK_CI_RUN_ID, OAK_CI_COMMIT, and OAK_CI_BRANCH, the job gets OAK_CI_ENVIRONMENT_NAME, OAK_CI_POLICY_VERSION, OAK_CI_AUTHORIZATION_ID, OAK_CI_WORKFLOW_PATH, and OAK_CI_WORKFLOW_HASH, so a deploy script can record exactly what was approved.

Two rules keep secrets from leaking sideways. A secret and a variable in one environment can't share a name. And an ordinary repo secret with the same name as any environment secret is withheld from ordinary jobs, so deleting a key from your CI secrets and adding it to an environment really moves it. Delete the ordinary copy.

Each line of a secret must be at least 4 characters, so the runner can mask it in logs.

Who can do what#

ReaderWriterOwner / admin
See an environment's settings, variables, and secret namesโœ“โœ“
Create, change, or delete an environment, its secrets, reviewers, and runnersโœ“
Deployif a reviewerif a reviewer, or reviewers aren't requiredif a reviewer, or reviewers aren't required

An API key that changes an environment needs the admin scope. One that deploys needs release.

Differences from GitHub#

GitHub ActionsOn Oak
A job with environment: runs on push and waits for approvalIt runs only when someone deploys it, at a commit they approved
Any runner with matching labelsOnly runners bound to the environment, which take no other jobs
Environment secrets plus repo secretsEnvironment secrets only
Deployment branch rules, wait timers, custom rulesBranch list, required reviewers, workflow pins; no wait timers yet
A workflow can mix environment and ordinary jobsAn environment workflow has exactly one job
environment: from an expressionA fixed name

API#

All under /api/{owner}/{name}/ci/, authenticated like the rest of the API.

Method and pathWhoDoes
GET environmentswrite accessList environments with their settings, variables, secret names, reviewers, and runners.
GET environments/{env}write accessOne environment.
PUT environments/{env}owner/adminCreate it, or change some settings: {"enabled", "required_reviewers", "branches": [...], "workflows": [{"path", "hash"}]}. Fields you leave out stay as they are. 201 when created.
DELETE environments/{env}owner/adminDelete it. Its runners go back to taking ordinary CI.
PUT / DELETE environments/{env}/secrets/{NAME}owner/adminSet ({"value": "..."}) or delete a secret.
PUT / DELETE environments/{env}/variables/{NAME}owner/adminSet ({"value": "..."}) or delete a variable.
PUT / DELETE environments/{env}/reviewers/{username}owner/adminAdd or remove a reviewer.
PUT / DELETE environments/{env}/runners/{runner-uuid}owner/adminBind or unbind one of the organization's runners.
GET environments/{env}/release-preview?workflow=&branch=read accessWhat a deploy would carry right now, whether you may approve it, and what's in the way.
POST release-authorizationsper the environmentApprove and start one deploy: {"environment", "branch", "commit", "workflow_path", "workflow_hash", "event": "manual", "policy_version"}, copied from the preview. Returns {"authorization_id", "run_id"}.

Something here wrong or missing? Run oak feedback -m "โ€ฆ" or email [email protected].