In the previous post I introduced Terraform landing zones on GitHub. A landing zone comes with OIDC authentication for secure provisioning to a target platform (Microsoft Azure in my demo), a starter Terraform configuration, GitHub Actions for common Terraform workflows, and a Terraform remote state backend.

A sample implementation of Terraform landing zones on GitHub discussed in this (and the previous) post is available on my GitHub.
The base landing zone covered in the previous post is enough to get your organization started if your Terraform footprint is small.
As your Terraform footprint grows, there is a need to govern what goes on in your Terraform environment. You will also want to standardize certain Terraform workflows. This follow-up blog post will cover a little bit about governance and standardization for your Terraform landing zones on GitHub.
The GitHub enterprise and organization#
At the top of the resource hierarchy on GitHub is the enterprise. An enterprise can have one or more organizations. Each organization contains repositories, projects, teams, and more.
I do not have access to my own GitHub Enterprise environment where I can run experiments, but I have my own organization. So I will not cover what you can do at an enterprise level in this post.
The purpose of an enterprise is to manage common rules across all of your organizations.
How many organizations should you have? The recommendation is one organization. For some contexts it makes sense to have two or more organizations.
The GitHub provider for Terraform1 allows you to manage some aspects of the enterprise and organization.
To use Terraform to manage GitHub organizations you have to make sure your authentication mechanism allows this. As in the previous post, I am using the GitHub CLI for authentication. I can manage my organization if the admin:org scope is included in my token.
I can refresh my current authentication to add the correct scope:
$ gh auth refresh -h github.com -s admin:org
Governance and Terraform standardization#
Introducing governance and standardization can mean many things.
Below I will add the following items as a baseline that can easily be extended:
- A common Terraform workflow.
- Global policies for Azure infrastructure.
Both of these are applied in the form of GitHub Actions workflows. Every commit to the main branch of a landing zone must pass both of these GitHub Actions workflows. If not, the change will be blocked.
To achieve this I want to use custom properties and rulesets.
Custom properties#
Custom properties are arbitrary string or boolean values you apply to repositories.
I create two custom properties for my organization:
- A single-valued string property named
providerwhich will be set to eitherazureoraws(in the following demo I only useazure). This is a great property to allow you to apply different rulesets for different cloud providers. - A boolean property named
terraformwhich is set totrueorfalseto indicate if this is a Terraform landing zone repository or not. After all, your organization might include other types of repositories.
These two custom properties can be configured using Terraform:
resource "github_organization_custom_properties" "provider" {
property_name = "provider"
value_type = "single_select"
required = false
description = "Target cloud provider"
allowed_values = [
"azure",
"aws"
]
}
resource "github_organization_custom_properties" "terraform" {
property_name = "terraform"
value_type = "true_false"
required = false
description = "Terraform landing zone"
}I update my landing zone module to set values of these custom properties for a landing zone repository:
resource "github_repository_custom_property" "provider" {
repository = github_repository.default.name
property_name = "provider"
property_type = "string"
property_value = ["azure"]
}
resource "github_repository_custom_property" "terraform" {
repository = github_repository.default.name
property_name = "terraform"
property_type = "true_false"
property_value = ["true"]
}Rulesets#
With the custom properties in place we can create rulesets where we enforce rules based on the custom properties set on a given repository.
There are three different types of rulesets you can apply:
- Branch rulesets.
- Tag rulesets.
- Push rulesets.
I want to use branch rulesets which allow me to set requirements for any change proposed to a given branch (or branches). In the previous blog post I built a workflow that runs terraform apply for any change that is merged to the main branch. For this reason, I now want to protect the main branch from bad changes.
The two rulesets I want to create are:
- Require a Terraform workflow that runs
terraform fmt -check. This command requires that the Terraform configuration is formatted according to the canonical format and style. This is a trivial example of what you could do, but nicely illustrates the usefulness of rulesets. This requirement should be in place for every repository with the custom propertyterraform = true. - Require a HashiCorp Sentinel workflow to evaluate the proposed change against one or more policies. I will use a single policy, but this can easily be extended to include any number of policies. This requirement should be in place for every repository with the custom property
provider = azure2.
The workflows are defined in separate repositories managed by a platform team. These do not have to be replicated into every landing zone repository. In fact, you want to avoid that anyone else except for the platform team can makes changes to these workflows.
Now for the bad news: at the time I am writing this rulesets based on custom properties are not supported by the GitHub provider for Terraform, see this open issue on GitHub.
If it would have been supported the resource would likely have looked like this:
resource "github_organization_ruleset" "terraform" {
name = "terraform"
target = "branch"
enforcement = "active"
conditions {
ref_name {
include = ["~DEFAULT_BRANCH"]
exclude = []
}
repository_property {
include = [
{
name = "terraform"
source = "custom"
property_values = ["true"]
}
]
exclude = []
}
}
rules {
required_workflows {
do_not_enforce_on_create = true
required_workflow {
repository_id = "<repo id>"
path = ".github/workflows/terraform-fmt.yaml"
ref = "main"
}
}
}
}As a workaround I can use the GitHub CLI to create the rulesets. See the scripts/rulesets.sh script in the GitHub repository for an example of what this could look like.
With the rulesets in place, opening a PR targeting the main branch will trigger the two required workflows automatically. If one of these workflows fail, the change is not allowed to be merged. In this way we protect the main branch from changes that violates our requirements and policies.
Workflow details#
I skipped over the GitHub Actions workflow details, but you can see the terraform fmt workflow and the Sentinel workflow for details.
If you want to learn more about HashiCorp Sentinel for policy-as-code I wrote a blog covering it a while back:

Key takeaways#
In the previous post I introduced the basics of a Terraform landing zone on GitHub. In this blog post I discussed two ways to scale this approach when your Terraform footprint increases:
- Introduce standardized Terraform workflow that any change to the main branch of a landing zone must fulfill.
- Introduce policy enforcement for governance.
Both build on custom properties and rulesets. My examples were trivial, but the principle is the same no matter what you want to enforce. This is, after all, not rocket science.
New versions of the GitHub provider for Terraform have been released since my last post. The current version is 6.10.1. I have noticed that GitHub has accelerated their release cadence for the provider. ↩︎
You can do the same thing for repositories targeting AWS, and configure the ruleset with a different workflow specific for AWS policies. ↩︎



