This is a part of my guide to how I manage multiple GitHub repositories. This post focuses on configuring repository settings on GitHub using Terraform. Back to main guide.

Repository Management with Terraform

I use Terraform to declaratively configure:

  • Repo metadata: description, topics, features
  • Branch settings: default branch and protection rules
  • GitHub Actions secrets: shared credentials for NuGet and Docker

Example Configuration

Github.tf defines:

locals {
  github_owner = "LordMike"
  github_token = "REPLACE_ME"

  repositories     = jsondecode(file("repos.json")).repositories
  repos            = keys(local.repositories)
  repos_public     = [for r in local.repos : r if lookup(local.repositories[r], "public", true)]
  nuget_repos      = [for r in local.repos : r if lookup(local.repositories[r], "nuget", true)]
  docker_repos     = [for r in local.repos : r if lookup(local.repositories[r], "docker", false)]
  docker_username  = "lordmike"
  docker_key       = "REPLACE_ME"
  nuget_key        = "REPLACE_ME"
}

provider "github" {
  owner = local.github_owner
  token = local.github_token
}

resource "github_repository" "repository" {
  for_each = toset(local.repos)
  name        = split("/", each.key)[1]
  description = lookup(local.repositories[each.key], "description", "")
  topics      = lookup(local.repositories[each.key], "topics", null)
  has_issues             = lookup(local.repositories[each.key], "has_issues", true)
  has_wiki               = lookup(local.repositories[each.key], "has_wiki", false)
  has_projects           = lookup(local.repositories[each.key], "has_projects", false)
  has_downloads          = lookup(local.repositories[each.key], "has_downloads", false)
  delete_branch_on_merge = lookup(local.repositories[each.key], "delete_branch_on_merge", true)
}

resource "github_branch_default" "default_branch" {
  for_each = toset(local.repos)
  repository = split("/", each.key)[1]
  branch     = "master"
}

resource "github_branch_protection" "protect_master" {
  for_each = toset(local.repos_public)
  repository_id = split("/", each.key)[1]
  pattern             = "master"
  enforce_admins      = false
  allows_deletions    = false
  allows_force_pushes = false
}

resource "github_actions_secret" "nuget_key" {
  for_each = toset(local.nuget_repos)
  repository      = split("/", each.key)[1]
  secret_name     = "NUGET_KEY"
  plaintext_value = local.nuget_key
}

resource "github_actions_secret" "docker_username" {
  for_each = toset(local.docker_repos)
  repository      = split("/", each.key)[1]
  secret_name     = "DOCKER_USERNAME"
  plaintext_value = local.docker_username
}

resource "github_actions_secret" "docker_key" {
  for_each = toset(local.docker_repos)
  repository      = split("/", each.key)[1]
  secret_name     = "DOCKER_KEY"
  plaintext_value = local.docker_key
}

Tips

  • Use for_each and filtered locals (e.g. docker_repos) to simplify resource declarations
  • Use lookup to define defaults and override per-repo settings

Terraform Imports (for existing repos)

Use terraform import with escaped resource keys:

terraform import 'github_repository.repository["LordMike/MBW.Utilities.ReflectedCast"]' LordMike/MBW.Utilities.ReflectedCast
terraform import 'github_branch_default.default_branch["LordMike/MBW.Utilities.ReflectedCast"]' LordMike/MBW.Utilities.ReflectedCast

Apply Changes

terraform refresh         # Fetch GitHub state
terraform apply           # Preview and apply
terraform apply -refresh=false  # Apply without refreshing state

This setup allows me to declaratively manage all my GitHub repos at scale.