Separate environments
Optional: hand this page to your coding agentThe steps work by hand too.Show the whole prompt
Read https://intentius.io/terragucci/guides/separate-environments/.
Check this repo's forge and cloud settings and say whether a branch job can reach the prod apply runner, secrets or role.
Read `environments` in terragucci.yml and the output of `npx terragucci config identities`, then the forge's environments, runners, runner groups, secrets or variables and branch protection through its API or CLI, and each apply role's trust through the cloud's CLI.
Answer each row of the page's table for this forge with what is set, and list each gap with the setting that closes it.
Do not change forge settings, runners, secrets, variables or trust policies yourself.
Never apply, approve (a pull request review or `terragucci approve`), override a policy denial (`terragucci override`), use `--mode apply`, or merge; never touch `.chant/allowed_signers` or `chant/lifecycle`.This page is optional reading for a repo that applies more than one environment, such as dev and prod. terragucci runs without any of it.
Result
Section titled “Result”A job from a branch, or from another environment, cannot run on prod’s apply runner, read prod’s secrets or assume prod’s apply role. Most of that is set on the forge and in the cloud. terragucci writes each environment’s apply jobs to match those settings, and checks for mistakes.
A check inside a job catches mistakes, such as a hand-edited workflow or a mistyped runner label. Someone who can edit the workflow can delete the check too, so it is the forge’s settings and the cloud’s trust that enforce the separation.
Prerequisites
Section titled “Prerequisites”| You need | Why |
|---|---|
Each environment’s roots named in environments, with its own apply role or identity and, where it has one, its own runner |
each environment then applies in jobs of its own, which the settings below can tell apart |
oidc set up |
the cloud’s trust can only check a token’s claims |
Settings per forge
Section titled “Settings per forge”Each row is a question, the setting that answers it, and who enforces that setting. Pick your forge; the other forge tabs on the site follow.
| Question | Setting | Enforced by |
|---|---|---|
| Which jobs may use the apply runners | a runner group for prod’s apply runners, open to selected repositories and, on plans that offer it, selected workflows. A label alone keeps nothing out: a branch can edit its workflow to ask for it | forge |
| Which refs may run an environment’s apply | an environment whose deployment branches are the default branch alone, with required reviewers if you want a person on each run. On a private repository, environments depend on your plan | forge |
| Which jobs see an environment’s secrets | environment secrets: only a job in that environment, past its branch rule, reads them. A repository secret reaches every workflow run in the repository, a branch’s push included | forge |
| What the cloud trust can require | the environment’s subject, repo:<owner>/<repo>:environment:<name> (GitHub’s OIDC claims), in AWS, GCP or Azure; Trust per environment has each cloud’s form |
cloud |
| What terragucci renders | each environment’s apply jobs with its runner as runs-on (labels or a runner group), its roles, environment: <forge_environment> and its oidc.audience |
terragucci |
| What terragucci checks | config check tips for shared runners, subjects and roles; check-job at the start of each apply job; config identities prints the subject and audience each role should trust, and forge check reads the environments, their branch rules, the apply runner group and each role’s trust and says which differ |
terragucci |
The environment’s subject does not name a ref, so the deployment branch rule is what keeps another branch’s job from taking it.
| Question | Setting | Enforced by |
|---|---|---|
| Which jobs may use the apply runners | protected runners, which run only jobs on protected branches and tags, with tags that only an environment’s runner names. Keep the list of protected branches short: each one’s pipeline can use them |
forge |
| Which refs may run an environment’s apply | a protected default branch, and a protected environment (Premium and Ultimate) that lets only the people who may deploy to it run its jobs | forge |
| Which jobs see an environment’s secrets | protected variables, which reach only protected refs, each with the forge environment’s name as its environment scope | forge |
| What the cloud trust can require | on AWS, the environment’s oidc.audience with sub limited to the default branch’s ref, since AWS cannot read GitLab’s environment claims; on GCP and Azure, the token’s environment, environment_protected and ref_protected claims, in a GCP attribute condition or an Azure claims expression. Trust per environment has each form |
cloud |
| What terragucci renders | each environment’s apply jobs with its runner as tags, its roles, environment: { name: <forge_environment> } and its oidc.audience in id_tokens |
terragucci |
| What terragucci checks | config check tips, terragucci-gitlab-audience among them; check-job at the start of each apply job, which also requires a protected ref; config identities prints the claims each role should trust, and forge check reads the environments’ protection, the apply runners’ protection, the variables’ scopes and each role’s trust and says which differ |
terragucci |
| Question | Setting | Enforced by |
|---|---|---|
| Which jobs may use the apply runners | a runner registered to the repository rather than an organization, user or the instance. Forgejo has no limit by workflow or branch, so any branch whose workflow asks for the runner’s labels can use it | forge |
| Which refs may run an environment’s apply | a protected branch per environment, which limits who can push to it. Forgejo has no deployment environments | forge |
| Which jobs see an environment’s secrets | none by environment: a repository’s secrets reach every workflow run in it, a branch’s push included | forge |
| What the cloud trust can require | the ref alone, in the subject repo:<owner>-<owner id>/<repo>-<repo id>:ref:refs/heads/<branch>, in AWS, GCP or Azure |
cloud |
| What terragucci renders | each environment’s apply jobs with its runner as runs-on, its roles and its oidc.audience |
terragucci |
| What terragucci checks | config check refuses forge_environment and gives the tip terragucci-shared-subject; check-job checks each apply job’s ref and runner; config identities prints each subject, and forge check reads the branch protections and each role’s trust and says which differ |
terragucci |
Read One ref, one subject below before you rely on these.
config identities prints, for each environment, the subject, audience, forge environment and runner of its plan, deciding, apply and drift jobs. The Terraform provider’s terragucci_environments and terragucci_trust_policy give the same values to the resources that make your roles, environments and runner groups.
Runner hosts
Section titled “Runner hosts”A runner that holds credentials of its own gives them to every job it takes, with no token and no trust condition in the way. Keep these off the hosts that run apply jobs, and off every other runner:
| Credential | Where it comes from | Keep it off by |
|---|---|---|
| An instance’s role | an AWS instance profile, a GCP VM’s service account, an Azure managed identity | giving the runner’s machine no role, or one that reaches nothing terragucci manages |
| The metadata endpoint | 169.254.169.254 and metadata.google.internal, which hand out the instance’s credentials |
blocking it from the job containers; on AWS, requiring IMDSv2 with a hop limit of 1 keeps a container on a bridge network from reaching it |
| Mounted keys | a volume the runner mounts into each job, or a key file on a host that runs jobs without a container | mounting nothing the jobs do not need; Forgejo’s container.valid_volumes left empty |
| Cloud config files | ~/.aws, ~/.config/gcloud, ~/.azure in the runner user’s home |
a runner user with no cloud login |
A runner that keeps files or processes between jobs can also carry one job’s state into the next. The tip terragucci-persistent-runner names apply runners that the plan jobs, or another environment’s apply jobs, could also take. Give them labels of their own, and use runners that start clean for each job, such as Forgejo’s ephemeral runners.
Forgejo: one ref, one subject
Section titled “Forgejo: one ref, one subject”A Forgejo token’s subject names the repository and the ref, nothing else. Two environments that apply from the same branch carry the same subject, so no cloud trust can tell their jobs apart: prod’s apply role trusts dev’s apply job as well. The runner and the secrets cannot be held to one environment either, as the table shows. Two ways out:
| Way | How | What it separates |
|---|---|---|
| A repository per environment | each environment’s roots in a repository of its own, with its own terragucci.yml, a runner registered to that repository and its own secrets; each apply role trusts its own repository’s subject |
runner, secrets and role |
| A protected branch per environment | apply.branches maps a branch such as release to prod’s roots; protect release, and have prod’s apply role trust ref:refs/heads/release alone |
the role; the runner and the repository’s secrets stay open to every branch’s workflow |
Check the live setup
Section titled “Check the live setup”Settings drift after setup: an environment’s branch rule gets widened, a runner stops being protected, a role’s trust grows a wildcard. terragucci forge check reads the forge’s and each cloud’s settings with read-only credentials, compares them with what config identities expects, and exits 1 when one differs. No pipeline job runs it; run it yourself, or from a scheduled job you add. The terragucci_forge_check data source returns the same rows for a Terraform check block.
An agent check
Section titled “An agent check”The prompt at the top of this page asks a coding agent to read the repo’s forge and cloud settings, answer each row of the table, and list the gaps. It changes nothing; you make the changes it lists.
- Trust per environment for each forge’s and cloud’s trust condition.
- Keep each environment’s roles to its own state for the roles’ policies.
- The threat model for what each guard covers and what it does not.
terragucciHelp setting it up: Intentius
These docs count page views and clicks with PostHog. They set no cookies, store nothing in your browser, and send nothing when your browser asks not to be tracked.