article

Orchestrating GitHub with Terraform

9 Sep 2025 | 6 min read

github and terraform

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_PAT
owner = "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 = string
sensitive = 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.repos
name = each.key
description = each.value.description
homepage_url = each.value.homepage_url
topics = each.value.topics
visibility = "public"
has_issues = true
has_downloads = false
has_discussions = false
has_projects = false
has_wiki = false
auto_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.

terraform plan output

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.teams
name = each.key
description = each.value.description
privacy = 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 matching
team_repo_permissions = flatten([
for repo_name, repo_config in local.repos : [
for team_name, team_config in local.teams : {
team_name = team_name
repo_name = repo_name
permission = 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].id
repository = github_repository.repos[each.value.repo_name].name
permission = 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 = true
depends_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"].name
target = "branch"
enforcement = "active"
conditions {
ref_name {
include = ["~DEFAULT_BRANCH"]
exclude = []
}
}
bypass_actors {
actor_id = 0
actor_type = "OrganizationAdmin"
bypass_mode = "always"
}
rules {
deletion = false
non_fast_forward = true
required_status_checks {
required_check {
context = "lint-pr"
}
}
pull_request {
required_approving_review_count = 2
required_review_thread_resolution = true
dismiss_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.

Patrick Heneise

Chief Problem Solver and Founder at Zentered

I'm a Software Enginer focusing on web applications and cloud native solutions. In 1990 I started playing on x386 PCs and crafted my first “website” around 1996. Since then I've worked with dozens of programming languages, frameworks, databases and things long forgotten. I have over 20 years professional experience in software solutions and have helped many people accomplish their projects.

pattern/design file