Add or change environments
This guide covers adding an environment to an existing pipeline, configuring each environment (required reviewers, wait timers, branch policy), and applying those settings to GitHub.
Add an environment
Section titled “Add an environment”environments is an ordered list; position defines the promotion ladder. By default the last environment is the release stage and the second-to-last is the prerelease stage. Each entry is a bare name (an environment with no extra settings) or an object carrying that environment’s name, optional role, and inline settings. Add an entry at the position you want, then regenerate:
environments: [dev, staging, prod]cascade generate-workflow -fcascade adds the new environment’s state.<env> entry automatically the next time orchestrate or promote finalizes; you never hand-author state:. Appending to the end of the list shifts which environment is the release stage, since that role defaults to the last position, so reorder deliberately rather than just appending if you want to keep an existing environment as production. To pin the release stage regardless of order, set role: release on that entry (see Roles).
Per-environment config
Section titled “Per-environment config”Per-environment settings live inline on an environment’s object entry, replacing the bare string. All fields are optional and additive; a bare-string entry carries none of them.
environments: - dev - name: prod gha_environment: production required_reviewers: ["octocat", "team/ops"] wait_timer: 10 branch_policy: protected| Field | Purpose |
|---|---|
gha_environment |
Maps this cascade environment to a GitHub Environment: native deployment records, environment_url, required reviewers, wait timers, env-scoped secrets. |
required_reviewers |
User or team slugs (for example octocat, team/ops) that may approve a deployment. |
wait_timer |
Delay in minutes before a job targeting this environment runs. GitHub accepts 0 to 43200 (30 days). |
branch_policy |
protected (protected branches only), custom (only branches/tags matching patterns), or all (no restriction). Empty is treated as all. |
Full field reference
Section titled “Full field reference”A few more fields round out the block, mostly for the custom branch policy or operator record-keeping:
| Field | Purpose |
|---|---|
branch_patterns |
Branch name patterns allowed to deploy when branch_policy: custom. |
tag_patterns |
Tag name patterns allowed to deploy when branch_policy: custom. |
secrets |
Expected env-scoped secret names only. cascade never stores or emits values; create them out of band. |
variables |
Expected env-scoped variable names only, same rule as secrets. |
environment_url |
URL reported to the GitHub Deployments API for this environment’s deployment status. |
GitHub Environment support is shipped: gha_environment drives native GitHub deployments and environment_url, and the fields above feed the environments command below. It lands in generated output today, not a “modeled but not emitted” state.
Pin a role explicitly
Section titled “Pin a role explicitly”By default the release and prerelease stages are positional: the last environment is the release stage, the second-to-last is prerelease. Set role: release or role: prerelease on an entry to declare that stage explicitly, so reordering the list no longer silently changes which environment publishes.
environments: - name: dev - name: prod role: release # prod publishes even if it is not last - name: canary role: prereleaserole is optional; when omitted the positional default applies unchanged. At most one entry may declare role: release and at most one role: prerelease.
Apply GitHub Environment settings with the environments command
Section titled “Apply GitHub Environment settings with the environments command”cascade environments reads your manifest and emits a per-environment configuration file for an operator to apply. cascade never calls the GitHub API itself:
cascade environmentsThe output has one entry per manifest environment, in manifest order:
| Key | Contents |
|---|---|
environments[].name |
The cascade environment name. |
environments[].gha_environment |
The GitHub Environment to configure. |
environments[].environment |
The body to PUT to the Environments API (wait_timer and the branch-policy fields cascade can fully form). |
environments[].operator_todo |
Guidance that is not part of the API body: reviewer slugs to resolve to numeric IDs, and secret/variable names to create. |
Apply each entry by sending only its .environment object:
cascade environments | jq -c '.environments[] | {gha_environment, environment}' | \ while read -r row; do env=$(jq -r .gha_environment <<<"$row") jq .environment <<<"$row" | \ gh api -X PUT "repos/OWNER/REPO/environments/$env" --input - doneRequired reviewers need a manual step: the Environments API takes numeric reviewer IDs, not slugs, so resolve each slug under operator_todo.required_reviewers and add it to the body’s reviewers array yourself. Secrets and variables are names only under operator_todo; create them through the environment-secrets and environment-variables APIs and set values yourself.
Flags: -c/--config (auto-detects .github/manifest.yaml), --manifest-key, -o/--output (write to a file instead of stdout).
Branch protection
Section titled “Branch protection”cascade branch-protection emits the JSON body for GitHub’s branch-protection API, built from the required Setup and Finalize jobs that run on every pipeline run:
cascade branch-protection --branch mainThe output has two keys: protection (the exact PUT body) and operator_todo. Apply it the same way:
cascade branch-protection | jq .protection | \ gh api -X PUT repos/OWNER/REPO/branches/main/protection --input -The required-status-checks list only ever contains the cascade-controlled Setup and Finalize jobs, since those are the only contexts cascade knows the exact name of; applying .protection as-is never blocks a pull request. Your validate, build, and deploy callback jobs are not required automatically, because cascade knows their display-name prefix but not the inner job name GitHub appends to form the real check-run context. Complete those from operator_todo.complete_these_contexts, which lists "<DisplayName> / <inner-job>" placeholders for you to fill in.
Pass --apply to PUT the body directly instead of emitting JSON; it requires a scoped, repo-admin token via --token (or GITHUB_TOKEN), plus --repo (default GITHUB_REPOSITORY) and --api-url (default GITHUB_API_URL, then https://api.github.com).
The runner-override caveat
Section titled “The runner-override caveat”Per-environment (and per-callback) runs_on is validated-only: cascade parses and schema-checks it, but never emits it into generated YAML. GitHub Actions forbids a runs-on: key on a job that calls a reusable workflow with uses:, and every cascade-generated caller job is exactly that shape. So cascade-owned jobs are hardcoded to ubuntu-latest, and there is no way to point one environment’s orchestration job at a different runner.
Your own callback workflows are unaffected: set runs-on: inside your workflow_call workflow’s job as you would in any other GitHub Actions workflow. The restriction only applies to the caller job cascade generates.
Prerequisite: Getting Started for a running pipeline to reshape. Next: Promote a release.
