Skip to content

Secrets and variables

The full list of what to set on the landing-zone repository, in one place. Most of the variables are written by the bootstrap; the secrets you set yourself.

Repository variables

Set under Settings, Secrets and variables, Actions, Variables.

Variable Required Purpose
AZURE_TENANT_ID yes Tenant id.
AZURE_SUBSCRIPTION_ID yes Subscription id for the state account and default context.
BACKEND_AZURE_RESOURCE_GROUP_NAME yes State storage resource group, read by root.hcl.
BACKEND_AZURE_STORAGE_ACCOUNT_NAME yes State storage account, read by root.hcl.
BACKEND_AZURE_STORAGE_ACCOUNT_CONTAINER_NAME yes State container, read by root.hcl.
RUNNER_LABEL for private state Self-hosted runner label. Falls back to ubuntu-latest.
TFPR_EXTRA_TOOLS for the gates Space-separated gate tools to install per run. infracost on the self-hosted posture (conftest and checkov are baked into the runner image); conftest checkov infracost on a GitHub-hosted posture.
CATALOG_APP_CLIENT_ID yes GitHub App client id. Authenticates the private engine checkout in reusable mode. The name is historical.
TF_PR_OPS_ALLOW_UNREVIEWED_APPLY only for a repo with no required reviews Set to true to allow /apply when the repository requires no approving reviews. Leave unset everywhere else.
TF_PR_OPS_PLAN_ENVIRONMENT optional Override the plan environment name. Default plan.
TF_PR_OPS_APPLY_ENVIRONMENT optional Override the apply environment name. Default apply.
TFPR_LOCK_STORAGE_ACCOUNT optional Storage account for the cross-PR unit lock table. Defaults to the BACKEND_AZURE_STORAGE_ACCOUNT_NAME account; required only for a plain Terraform repo with no backend variables.
TF_PR_OPS_DISABLE_LOCKS optional Set to true to turn cross-PR unit locking off. Leave unset everywhere else.

TF_PR_OPS_ALLOW_UNREVIEWED_APPLY is a real relaxation

The engine reads GitHub's review decision to gate /apply. A repository that requires no approving reviews reports an empty decision, which the engine refuses, so /apply never runs and there is nothing to approve to unblock it. Only that case needs this variable.

An empty decision is refused rather than allowed because it cannot be told apart from a ruleset that was deleted. Setting the variable gives that up knowingly. The usual reason is a single-writer organisation, which cannot self-approve on GitHub and so sets the required review count to 0 on purpose. Everywhere else, set the required review count instead and leave this unset.

Variables or ARM secrets

The workflow reads the identity as vars.AZURE_* || secrets.ARM_*. Prefer the AZURE_* variables written by the bootstrap. The ARM_* secrets exist as a fallback for demos that set the identity by hand.

Repository secrets

Set under Settings, Secrets and variables, Actions, Secrets.

Secret Required Purpose
CATALOG_APP_PRIVATE_KEY yes GitHub App private key. Required in reusable mode: it authenticates the private engine checkout. The name is historical.
INFRACOST_API_KEY for the cost gate Hosted pricing API key. Until set, the infracost gate notes it and skips.
ARM_CLIENT_ID fallback only Identity client id when not using AZURE_* variables.
ARM_TENANT_ID fallback only Tenant id fallback.
ARM_SUBSCRIPTION_ID fallback only Subscription id fallback.

GITHUB_TOKEN is provided automatically by GitHub Actions and posts the comments, checks, and statuses. You do not set it.

Why the CATALOG_APP_* names

The GitHub App originally fetched modules from a private Terragrunt catalog. That consumption model was dropped in July 2026: the custom Azure Policy library is now vendored into the landing-zone repository, and the App's only remaining job is authenticating the private engine checkout. The names were kept so existing repositories keep working. This is the whole story; other pages link here rather than repeating it.

Environments

Two GitHub environments on the repo, each with an Azure federated credential:

Environment Identity Used by
plan Reader (read-only) PR plan jobs and the daily drift plan.
apply Owner The apply job.

Each environment carries its own AZURE_CLIENT_ID variable, pointing at that environment's identity. The bootstrap sets both. It is an environment variable, not a repository variable: a single repository-level value would give the plan jobs the Owner identity, or the apply jobs the Reader one.

The OIDC subject binds the repository and the environment name, built with immutable repository ids (repo:org@id/repo@id:environment:env) for repos created after GitHub's rollout, so keep the names stable. If you must rename them, set TF_PR_OPS_PLAN_ENVIRONMENT and TF_PR_OPS_APPLY_ENVIRONMENT and update the federated credential subjects to match.

Branch protection

Make tf-pr-ops / merge-gate a required status check on the main branch, and set the required reviews you want /apply to respect.

The bootstrap provisions a require-approved-pr-to-main ruleset that requires an approved pull request. It adds the required status check only when its required_status_checks variable is set, and that variable is empty by default, so by default the merge gate is not required. Set the variable to ["tf-pr-ops / merge-gate"] before running the bootstrap, or add the check by hand afterwards.

The cost gate key

The infracost gate uses the legacy infracost line. Get a static API key from the Infracost dashboard, set it as INFRACOST_API_KEY, and add infracost to TFPR_EXTRA_TOOLS. See the gates for detail and the data residency note.