TLDR;
Treat your GitHub organization like infrastructure. Put repos, teams, and
permissions under version control, review changes with pull requests, and let
Terraform apply them safely and repeatably. Start with a small set of repos and
teams, lean on locals
and for_each
to stay DRY, and automate everything in
Terraform Cloud so org changes are auditable.
Why Terraform for GitHub?
It's not news to store Infrastructure as Code (IaC) in your GitHub repo to orchestrate Google Cloud Platform, AWS or Azure resources. But did you know that you can also manage your GitHub organization with Terraform?
Your GitHub org is infrastructure. Managing it with Terraform brings version control, code review, and repeatable automation to repositories, teams, and permissions. Define sub-teams and access policies declaratively, derive permissions from topics or a simple assignment file, and track every change in git. It replaces brittle UI clicks with auditable, scalable workflows as your organization grows.
Getting Started
Prerequisites
- Terraform or Terraform Cloud (free tier covers small orgs)
- Admin access on your GitHub organization
- A GitHub Personal Access Token (PAT) with org admin/repo scopes
Initialize Terraform & GitHub
Create a new repository—for example, infrastructure
—and add a main.tf
with
the following content:
provider "github" {token = var.GH_PATowner = "boulderjs-playground"}provider "tfe" {}
Define the variable for the GitHub PAT in a variables.tf
file:
variable "GH_PAT" {description = "GitHub Personal Access Token"type = stringsensitive = true}
Create a versions.tf
file to specify the required providers and Terraform
version:
terraform {required_providers {github = {source = "integrations/github"version = ">= 6.6.0"}tfe = {source = "hashicorp/tfe"version = "0.68.2"}}}
You'll also need to create the Terraform Cloud token (in the web UI, settings)
and store an Environment Variable named TFE_TOKEN
in your Terraform Cloud
workspace (Variables section).
That’s the minimal setup.
Create your repos
Now you can start creating your repository structure. You can define more
variables in the locals
block, like topics, homepage URL, etc.
locals {repos = {"website" = {description = "BoulderJS website and blog"homepage_url = ""topics = ["frontend", "developers"]},"events" = {description = "BoulderJS Events"homepage_url = ""topics = ["events", "admins"]},"talks" = {description = "Talks & Proposals"homepage_url = ""topics = ["events", "members"]}}}resource "github_repository" "repos" {for_each = local.reposname = each.keydescription = each.value.descriptionhomepage_url = each.value.homepage_urltopics = each.value.topicsvisibility = "public"has_issues = truehas_downloads = falsehas_discussions = falsehas_projects = falsehas_wiki = falseauto_init = true}
There are several more options you can set. Check the
Terraform GitHub Provider documentation
for more details. If you already have an org with several repos, you can
create import
blocks.

Create teams
Same as with the repos, you can define your teams in a locals
block and create
them with a for_each
loop.
locals {teams = {"admins" = {description = "BoulderJS Admins"privacy = "closed"},"advocates" = {description = "BoulderJS Advocates"privacy = "closed"},"members" = {description = "BoulderJS Members"privacy = "closed"},"developers" = {description = "Developers"privacy = "closed"}}}resource "github_team" "parents" {for_each = local.teamsname = each.keydescription = each.value.descriptionprivacy = each.value.privacy}
You can even create & manage more complex "sub-team" structures by adding a
parent_team_id
attribute to the github_team
resource. GitHub Teams are
awesome for managing permissions and access to repositories, mentioning an
entire team in issues or pull requests or even auto-assigning reviewers. Via the
CODEOWNERS file and
GitHub Team Settings
you can define the review request delegation algorithm (e.g., Round Robin) for
pull request reviews.
Assign repositories to teams
Last but not least, you can assign your repositories to the teams you've created.
locals {# Create team-repo permissions based on topic matchingteam_repo_permissions = flatten([for repo_name, repo_config in local.repos : [for team_name, team_config in local.teams : {team_name = team_namerepo_name = repo_namepermission = contains(repo_config.topics, team_name) ? "maintain" : "pull"}]])}resource "github_team_repository" "permissions" {for_each = {for perm in local.team_repo_permissions :"${perm.team_name}-${perm.repo_name}" => perm}team_id = github_team.parents[each.value.team_name].idrepository = github_repository.repos[each.value.repo_name].namepermission = each.value.permission}
This uses the topics
attribute from the local.repos
definition to match
teams to repos. If a topic matches a team name, that team gets maintain
permissions, otherwise just pull
access.
You can use other strategies, like a permissions file, or a more complex matching algorithm.
Apply your configuration
Just add & commit all files to your git repo and push it to GitHub. You can set
up Terraform Cloud to (automatically) plan & apply your configuration on every
push to the main branch or run plans on feature branches. Make sure to add the
GH_PAT
variable in your Terraform Cloud workspace.
More possibilities
GitHub Issue Templates
You can use the github_repository_file
resource to create issue templates in
your repositories. Just create a local folder with your templates and use the
for_each
loop to create them in each repository.
resource "github_repository_file" "talks_issue_template_config" {repository = "talks"branch = "main"file = ".github/ISSUE_TEMPLATE/config.yml"content = file("${path.module}/files/ISSUE_TEMPLATE/config.yml")commit_message = "Add issue template config"commit_author = "Terraform"commit_email = "[email protected]"overwrite_on_create = truedepends_on = [github_repository.repos]}
Branch Protection Rules
You can enforce branch protection rules on your repositories to ensure code quality and consistency. Here's an example of a strict ruleset that requires status checks and pull request reviews before merging.
resource "github_repository_ruleset" "strict" {name = "strict"repository = github_repository.repos["website"].nametarget = "branch"enforcement = "active"conditions {ref_name {include = ["~DEFAULT_BRANCH"]exclude = []}}bypass_actors {actor_id = 0actor_type = "OrganizationAdmin"bypass_mode = "always"}rules {deletion = falsenon_fast_forward = truerequired_status_checks {required_check {context = "lint-pr"}}pull_request {required_approving_review_count = 2required_review_thread_resolution = truedismiss_stale_reviews_on_push = true}}}
Tips & lessons learned
Keep your GitHub configuration separate from your application environments; a
dedicated repository or workspace makes boundaries—and audits—clear. Terraform
Cloud is an excellent home for state and for running plans and applies, which
beats juggling local state and ad‑hoc workflows. Lean on for_each
and locals
to keep configurations DRY and scalable, and package common patterns as modules
so you can reuse them confidently across teams and repos.
Summary & Outlook
Using Terraform to manage GitHub repositories, teams, and permissions can greatly simplify the process of maintaining a well-organized and secure development environment. By defining your infrastructure as code, you can easily track changes, collaborate with your team, and ensure consistency across your organization. With the power of Terraform and the GitHub provider, you can automate many aspects of your GitHub management, allowing you to focus on what matters most: building great software.
If you have questions or want a second set of eyes on your setup, reach out on Bluesky or start a discussion on GitHub.