Skip to content

Control repo

llms.txtlists every page for an agent

Twenty repos with twenty pipelines drift apart: one runs an older binary, one skips the shared policy, one never got the drift schedule. A control repo keeps them alike without editing each one. You change one file, and every project that changes gets a pull request its own team merges.

A control repo is optional. One repo works on its own, with its own terragucci.yml; a control repo is for keeping many repos alike.

A control repo and its projectsThe control repo holds one terragucci.yml with defaults and three projects. reconcile previews each project, then with --mode apply opens a pull request in the GitHub project and a merge request in the GitLab project, whose pipelines would change, and nothing in the Forgejo project, which is current. Each team reviews and merges its own request, and each project's own pipeline checks, plans and applies, with its state and approvals in that project.control repoterragucci.ymldefaults: every projectprojects: three repos, three forgesreconcilepreview, then --mode applyacme/infraGitHubpull requestthe team reviewsand mergesown pipelinecheck, plan,waves, applyacme/networkGitLabmerge requestthe team reviewsand mergesown pipelinecheck, plan,waves, applyacme/edgeForgejoalready currentnothing to mergeown pipelinecheck, plan,waves, applystate, approvals and applies stay in each project

The control repo’s terragucci.yml lists the projects and what they share.

defaults:
binary: tofu
policy:
source: git+https://github.com/acme/policy.git@v3
projects:
github.com/acme/infra:
roots: ["envs/*/*"]
gitlab.example.com/platform/network:
drift: "17 4 * * *"
codeberg.org/acme/edge: {}
Key Holds
projects each repo by <host>/<owner>/<name> (a GitLab group path works too), with the keys that differ for it; a repo left out is never touched
defaults the settings every project gets: any key but url, which names one repo, and rollouts
a project’s forge, url, token_env the forge of a host terragucci cannot name, a clone URL off https or the default port, the variable that holds its token

A project’s settings are terragucci’s built-in defaults, then defaults, then the project’s own keys. The maps env, oidc, policy, waves, apply, respond, terragrunt and generate merge key by key; every other key is replaced whole. config check refuses a top-level key outside defaults and projects, and defaults with no projects. Keys lists each one, and Manage the control repo with Terraform writes the file with a provider instead.

terragucci reconcile clones each project and writes its pipeline in the clone, as init would with the project’s settings.

Run Result per project
reconcile, the preview unchanged or would change, the files, and setup tips; nothing is written
reconcile --mode apply for a project that changes, a commit on terragucci/pipeline and a pull request (a merge request on GitLab), opened or updated; an unchanged project gets none

--mode apply never writes a default branch and runs no terraform apply. One project failing does not stop the others; the command names it and exits 1. A later run opens a pull request only where something changed.

The pull request carries:

File Holds
the pipeline file for the project’s forge the keys flags and the job carry, such as binary, version, apply, locks, oidc, notify
terragucci.yml, marked as written by reconcile the keys the jobs read from the repo, such as policy, reports, approval, gate, parallelism
each root’s backend, provider and version files with generate set

A file for many repos sorts every key into the two ways.

Stays in each project Detail
the roots, state and backend reconcile writes only the files above
the merge the project’s team reviews and merges the pull request
plan, approvals and apply the project’s own pipeline runs them, and approvals land on its own chant/lifecycle
its own terragucci.yml kept; when a key the jobs read differs from the control repo’s, reconcile fails that project and names the key

unlock-state and ephemeral run in a project’s checkout, never in the control repo.

From the control repo Does
reconcile one pipeline pull request per project that changes
defaults.policy.source one policy repo, at a pinned ref, for every project
rollout wave 1 holds every project’s canaries, then each project in config order, a pull request per project per wave; respond rollout --mode apply on a schedule continues it
estate one page from each project’s own reports bucket, written under defaults.reports
audit each project’s approvals from its repo and its reports, in one record under defaults.reports

One file can list projects on GitHub, GitLab and Forgejo. The host names the forge (github.com, gitlab.com, codeberg.org, and hosts starting github., gitlab., forgejo. or gitea.); set forge for any other. Each forge’s token comes from GITHUB_TOKEN, GITLAB_TOKEN or FORGEJO_TOKEN, or the variable a project’s token_env names.

You have Use
one repo its own terragucci.yml and init; reconcile refuses a file with no projects
several repos that should share a binary, policy, gates or reports a control repo
a project that needs a different value the key under that project, not defaults
a repo terragucci should not touch leave it out of projects

terragucci

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.