Terraform project to provision and manage GitHub repositories for personal accounts with declarative configuration and modular design.
- 🏗️ Declarative Repository Management: Define all repository settings in a single configuration file
- 🔒 Branch Protection Rules: Comprehensive branch protection with customizable security settings
- 🧩 Modular Design: Reusable modules for repositories and branch protection
- ☁️ Terraform Cloud Integration: Remote state management with Terraform Cloud free tier
- 🔐 Security First: Built-in defaults for security best practices
- 📝 Fully Composable: Each repository is independently configured
-
GitHub Personal Access Token (PAT)
- Go to GitHub Settings → Developer settings → Personal access tokens → Tokens (classic)
- Generate new token with the following scopes:
repo(Full control of private repositories)admin:repo_hook(Full control of repository hooks)delete_repo(Delete repositories)
- Save the token securely - you'll need it for authentication
-
Terraform Cloud Account (Free)
- Sign up at app.terraform.io
- Create an organization
- Update
versions.tfwith your organization name
-
Terraform CLI
- Install Terraform >= 1.6.0
- Download from terraform.io
github-infra/
├── modules/
│ ├── repository/ # Repository creation module
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── branch-protection/ # Branch protection module
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── main.tf # Root module orchestration
├── variables.tf # Root variables
├── outputs.tf # Root outputs
├── providers.tf # Provider configuration
├── versions.tf # Terraform and provider versions
├── terraform.tfvars.example # Example configuration
├── .gitignore # Git ignore rules
└── README.md # This file
# Clone or navigate to this repository
cd github-infra
# Copy the example configuration
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your settings
vim terraform.tfvarsEdit versions.tf and replace REPLACE_WITH_YOUR_ORG with your Terraform Cloud organization name:
cloud {
organization = "your-terraform-cloud-org"
workspaces {
name = "github-infra"
}
}# GitHub Personal Access Token
export GITHUB_TOKEN="your_github_personal_access_token"
# GitHub owner (your username) - alternative to setting in tfvars
export TF_VAR_github_owner="your-github-username"# Initialize Terraform (will prompt for Terraform Cloud login)
terraform init
# Validate configuration
terraform validate
# Format code
terraform fmt -recursiveEdit terraform.tfvars to define your repositories:
github_owner = "your-github-username"
repositories = {
"my-project" = {
description = "My awesome project"
visibility = "public"
has_issues = true
auto_init = true
gitignore_template = "Node"
license_template = "mit"
topics = ["nodejs", "api"]
branch_protection = {
pattern = "main"
require_signed_commits = true
require_conversation_resolution = true
required_pull_request_reviews = {
required_approving_review_count = 1
dismiss_stale_reviews = true
}
}
}
}# Preview changes
terraform plan
# Apply changes
terraform apply
# View outputs
terraform output- Add a new entry to the
repositoriesmap interraform.tfvars - Run
terraform planto preview - Run
terraform applyto create the repository
- Remove the repository entry from
terraform.tfvars - Run
terraform planto preview (repository will be archived by default) - Run
terraform applyto archive the repository
| Field | Type | Default | Description |
|---|---|---|---|
description |
string | "" |
Repository description |
visibility |
string | "private" |
Repository visibility: public, private, or internal |
has_issues |
bool | true |
Enable GitHub Issues |
has_discussions |
bool | false |
Enable GitHub Discussions |
has_projects |
bool | false |
Enable GitHub Projects |
has_wiki |
bool | false |
Enable GitHub Wiki |
has_downloads |
bool | false |
Enable downloads |
auto_init |
bool | false |
Initialize with README |
gitignore_template |
string | null |
Gitignore template (e.g., "Node", "Python", "Go") |
license_template |
string | null |
License template (e.g., "mit", "apache-2.0", "gpl-3.0") |
topics |
list(string) | [] |
Repository topics/tags |
allow_merge_commit |
bool | true |
Allow merge commits |
allow_squash_merge |
bool | true |
Allow squash merging |
allow_rebase_merge |
bool | true |
Allow rebase merging |
allow_auto_merge |
bool | false |
Allow auto-merge on pull requests |
delete_branch_on_merge |
bool | true |
Automatically delete head branches after merge |
vulnerability_alerts |
bool | true |
Enable security alerts |
archive_on_destroy |
bool | true |
Archive instead of delete on destroy |
is_template |
bool | false |
Make this a template repository |
homepage_url |
string | null |
Repository homepage URL |
Branch protection is optional. Omit the branch_protection block to create a repository without protection.
| Field | Type | Default | Description |
|---|---|---|---|
pattern |
string | required | Branch pattern (e.g., "main", "develop") |
enforce_admins |
bool | false |
Enforce rules for administrators |
require_signed_commits |
bool | false |
Require signed commits |
require_conversation_resolution |
bool | false |
Require conversation resolution before merge |
allows_force_pushes |
bool | false |
Allow force pushes |
allows_deletions |
bool | false |
Allow branch deletion |
lock_branch |
bool | false |
Lock branch (read-only) |
required_status_checks |
object | null |
Required status checks configuration |
required_pull_request_reviews |
object | null |
Required PR reviews configuration |
restrict_pushes |
object | null |
Push restrictions configuration |
required_status_checks = {
strict = true # Require branches to be up to date
contexts = ["ci/tests", "ci/lint"] # Status checks that must pass
}required_pull_request_reviews = {
required_approving_review_count = 2 # Number of required approvals (1-6)
dismiss_stale_reviews = true # Dismiss approvals on new commits
require_code_owner_reviews = true # Require review from code owners
require_last_push_approval = true # Require approval of latest push
}"secure-app" = {
description = "High-security application"
visibility = "private"
vulnerability_alerts = true
branch_protection = {
pattern = "main"
enforce_admins = true
require_signed_commits = true
require_conversation_resolution = true
required_pull_request_reviews = {
required_approving_review_count = 2
dismiss_stale_reviews = true
require_code_owner_reviews = true
require_last_push_approval = true
}
required_status_checks = {
strict = true
contexts = ["security-scan", "tests", "lint"]
}
}
}"my-notes" = {
description = "Personal notes"
visibility = "private"
has_issues = true
auto_init = true
topics = ["personal", "notes"]
# No branch protection - simple repo
}"awesome-lib" = {
description = "An awesome open source library"
visibility = "public"
has_issues = true
has_discussions = true
homepage_url = "https://awesome-lib.dev"
license_template = "mit"
gitignore_template = "Node"
topics = ["javascript", "library", "open-source"]
branch_protection = {
pattern = "main"
required_pull_request_reviews = {
required_approving_review_count = 1
dismiss_stale_reviews = true
}
required_status_checks = {
strict = true
contexts = ["tests", "lint"]
}
}
}After applying, Terraform outputs the following information:
repositories: Complete details of all created repositoriesrepository_urls: Quick map of repository names to URLsprotected_branches: List of repositories with branch protection
View outputs:
terraform output
terraform output repositories
terraform output repository_urls-
Never commit sensitive data
- Keep
terraform.tfvarsout of version control (already in.gitignore) - Use environment variables for tokens
- Rotate tokens regularly
- Keep
-
Use branch protection
- Always protect
mainand production branches - Require pull request reviews
- Enable signed commits for sensitive repositories
- Always protect
-
Enable security features
- Enable
vulnerability_alerts - Use
privatevisibility by default - Set
archive_on_destroy = trueto prevent accidental deletion
- Enable
-
Terraform Cloud security
- Enable 2FA on Terraform Cloud account
- Use workspace permissions appropriately
- Review state file access
# Verify token is set
echo $GITHUB_TOKEN
# Test GitHub API access
curl -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/user# Login to Terraform Cloud
terraform login
# Or manually create ~/.terraform.d/credentials.tfrc.json# Refresh state
terraform refresh
# View current state
terraform show
# Import existing repository (if needed)
terraform import 'module.repositories["repo-name"].github_repository.this' owner/repo-name# Check configuration
terraform validate
# Format code
terraform fmt -recursive
# Show plan with detailed logs
TF_LOG=DEBUG terraform plan# Update providers
terraform init -upgrade
# Check for updates
terraform versionTerraform Cloud automatically versions state. To download:
# Download state
terraform state pull > terraform.tfstate.backupThis is a personal infrastructure project. Feel free to fork and adapt for your own use.
MIT License - Feel free to use this as a template for your own infrastructure.
Happy Infrastructure Coding! 🚀