Self-hosted runner and the engine GitHub App¶
Why a self-hosted runner¶
Terraform state lives in a storage account behind a firewall that allows only known addresses. The jobs that read and write state (plan, apply, and the drift plan) run on a self-hosted runner on a private network with a fixed egress address that the state firewall allows.
The jobs that only talk to the GitHub API (event parsing, project selection, the
merge gate, and drift issue reporting) run on hosted ubuntu-latest runners,
because the self-hosted runner is network-scoped and does not carry gh.
Set the runner label with the RUNNER_LABEL repository variable. When it is not
set, jobs fall back to ubuntu-latest, which is fine for a demo that does not
need private state access.
The runner image¶
The self-hosted runner image bakes in the tools the gates need so nothing is installed on the critical path:
- conftest and checkov are baked into the image. checkov is a Python tool
with no static binary, so baking it is the production path. On the self-hosted
posture, set
TFPR_EXTRA_TOOLSto justinfracost. - infracost is a static binary that the workflow installs per run when
TFPR_EXTRA_TOOLSincludesinfracost. It installs to a user-writable path because the runner runs as a non-root user.
On a GitHub-hosted posture there is no baked image, so TFPR_EXTRA_TOOLS installs
all three per run: conftest checkov infracost.
The image is built by the bootstrap in nrit-alz-bootstrap/runner-image/github-runner-aca.
Runner sizing¶
Runner replicas run at 2 vCPU and 4Gi with a 7200-second replica timeout.
This is deliberate. The AVM module defaults (1 vCPU, 2Gi, 1800s) OOM-hang the heavy
ALZ policy-library plans and kill the runner mid-job, which leaves stale state
locks. The bootstrap sets the larger size through
container_app_container_cpu, container_app_container_memory, and
container_app_replica_timeout.
The engine GitHub App¶
nrit-tf-pr-ops is a private repository, so the reusable workflow needs a
credential to check its own scripts out at engine_ref. A GitHub App mints that
short-lived token. That is the App's only job.
Wire it with:
CATALOG_APP_CLIENT_ID(repository variable): the GitHub App client id. The bootstrap sets this.CATALOG_APP_PRIVATE_KEY(repository secret): the GitHub App private key.
The CATALOG_APP_* names are historical and were kept so existing repositories
keep working; see Secrets and variables for the
background. The App installation must grant read access
to nrit-tf-pr-ops. Because reusable mode depends on the engine checkout, both
values are required on every landing-zone repository.
The private key is a real secret
Store CATALOG_APP_PRIVATE_KEY only as a GitHub Actions secret. Never put it
in a tfvars file or commit it. Rotating it means updating the secret; the
workflow reads it fresh on every run.
Cross-repo access¶
The landing-zone repo and the engine are separate private repositories in the same
organisation. Set nrit-tf-pr-ops to organisation-accessible so the callers may
invoke its reusable workflows. The GitHub App token described above is what
authenticates the engine checkout itself.
Units source public Azure Verified Modules only, so no private module fetch happens on the plan path and nothing else needs a cross-repo grant.
Next: Secrets and variables.