In a previous post, I wrote about the challenges of building a public GitHub organization correctly: The need for structure, consistency, and enforcement rather than relying on memory.
What I didn’t cover was how we actually solved it.
The short answer: We treat the organization as infrastructure. Repositories, teams, branch protection rules, labels, permissions, all of it is declared as code, reviewed through pull requests, and applied automatically when changes are merged. This repository is the org-control-plane.
When an organization is small, doing things manually feels fine. You create a repository, configure a few settings, add the right team. It takes a few minutes and you move on.
But over time, this approach quietly accumulates problems.
Settings drift; one repository with security scanning enabled, while another was created before that became the standard; a team that should have read-only access was accidentally given write access months ago, and nobody remembers why.
None of these problems are catastrophic on their own. But together, they make the organization harder to reason about and harder to audit. And when something goes wrong, you have no record of what changed or who changed it.
The solution isn’t more documentation or better communication, it’s making the state of the organization explicit and version-controlled.
The control plane manages the entire neteye-platform GitHub organization using Terraform and Terragrunt. Everything is declared as YAML, and Terraform reconciles that declaration against the real state of the organization on every apply.
The repository is organized into a few top-level units, organization settings, teams, and repositories grouped by category. Each is an independent Terragrunt module that can be planned and applied on its own or together with the rest.
Adding a new repository is just a YAML file:
name: my-new-tool
description: A tool for doing things.
homepage_url: https://example.com
Everything else is inherited from defaults. Which brings us to arguably the most valuable part of the design.
Settings flow through three tiers: org-wide defaults, category defaults, and per-repository overrides. A repository only needs to declare what’s different.
This makes the common case trivially cheap. If a repository fits within an existing category, the YAML file can be two or three lines. The security baseline, the branch protection rules, the team access, the labels: it’s all inherited automatically.
More importantly, it makes enforcement reliable. A policy defined at the org level applies to every repository, including ones created next month. There’s no risk of forgetting to enable something on a new repo, because there’s nothing to forget.
When exceptions are needed, they’re explicit. A repository can extend inherited settings rather than replace them. This means the diff between “what a repository does” and “what every repository does by default” is always visible and intentional.
The org-wide defaults include branch rulesets that apply to every repository from the moment it’s created: Things like required code reviews, signed commits, and a set of required status checks covering security scanning, style and formatting, and custom validation. No manual setup per repository, no drift over time.
Categories and individual repositories can build on top of this baseline, but they can’t accidentally skip it.
This is the fundamental advantage of codifying the organization: The right defaults are the only defaults. You don’t need to rely on the person creating a repository to remember to enable security scanning, because it’s already there.
When a pull request is opened against the control plane, the CI runs terragrunt plan and posts the results as a comment. Before anyone approves, they can see exactly what Terraform intends to change, which repositories will be created, which settings will be updated, and which access rules will be modified.
When the PR is merged, a second workflow applies those changes to the live organization.
This matters more than it might seem. Changes to the organization go through the same review process as changes to application code. There’s an audit trail. If something breaks or a question comes up six months later, you can look at the git history and understand exactly what changed and why.
There’s also no “emergency direct change in the GitHub UI.” If something needs to change, it needs a PR. That constraint has been uncomfortable exactly once, and useful many times.
The initial investment is real: setting up the Terraform modules, the Terragrunt configuration, the CI workflows, and the remote state backend. It takes time.
But after that setup, the ongoing cost is minimal. Adding a repository is writing a YAML file and opening a PR. Changing a setting across the entire organization is editing one file. Auditing who has access to what is reading the repository.
Compare that to the alternative: Logging into the GitHub UI, navigating to each repository, making manual changes, and hoping nothing was missed. As the number of repositories grows, that approach doesn’t scale. The IaC approach scales almost for free.
A GitHub organization is infrastructure. It has settings that drift, permissions that accumulate, and rules that become inconsistent when managed manually over time.
Treating it like infrastructure, with version control, code review, and automated apply, solves those problems without requiring significant ongoing effort. The organization becomes auditable, reproducible, and easy to reason about.
And when someone asks “why does this repository have this setting?”, the answer is always just a git log away.
The org-control-plane repository is publicly available in the neteye-platform organization.
Did you find this article interesting? Are you an “under the hood” kind of person? We’re really big on automation and we’re always looking for people in a similar vein to fill roles like this one as well as other roles here at Würth IT Italy.