Skip to main content

Terraform: Adding a Tenant

A Tenant is an individual workload or application that lives inside a Business Unit. While a BU provides the organizational grouping (folders, shared config), a Tenant is where actual GCP projects and resources are created.

This guide walks through creating a Tenant via Backstage and explains what gets provisioned.

What Is a Tenant?

A Tenant represents a distinct workload — a microservice, a data pipeline, a web application, or any other deployable unit. When you create a Tenant, the platform provisions:

  • Per-environment GCP projects (e.g., prj-governance-d-f6y39 for dev, prj-governance-n-f6y39 for non-prod, prj-governance-p-f6y39 for prod)
  • A GitHub repository scaffolded from foundations-template
  • Per-environment Workload Identity Federation (WIF) configurations and service accounts
  • Per-environment Terraform Cloud workspaces for deploying the tenant's infrastructure
  • Optional GAR repositories for container images and packages
  • Optional budget alerts for cost management

Key Difference from Business Units

Business UnitTenant
GCP resourceFoldersProjects
WIF modelCross-environment (single SA)Per-environment (separate SAs)
BudgetsNot supportedSupported per environment
ContainsTenantsResources (Cloud Run, GCS, etc.)

Creating a Tenant via Backstage

The recommended way to create a Tenant is through the Backstage developer portal using the create-tenant template.

Prerequisites

  • An existing Business Unit (see Adding a Business Unit)
  • Access to the Backstage portal
  • Membership in the GitHub team that owns the parent BU

The Two-Step Wizard

The Backstage create-tenant template uses a two-step wizard:

Step 1: Basic Configuration

Fill in the tenant details:

FieldDescriptionExample
Tenant NameShort identifier for the workloadgovernance
Business UnitThe parent BU to create this tenant indata
DescriptionHuman-readable descriptionData governance service
OwnerGitHub team that owns this tenant@badal-io/data-team
EnvironmentsWhich environments to provisiondev, non-prod, prod
Branching ModelHow CI/CD branches map to environmentstrunk-based or environment-based

Step 2: Terraform Module Editor

The second step presents a custom GithubTerraformModuleEditor UI component where you can:

  • Select which infrastructure modules to include (Cloud Run, GCS, BigQuery, etc.)
  • Configure module parameters visually
  • Preview the generated Terraform code

This editor generates the initial main.tf for the tenant repository.

What Happens Next

  1. Backstage creates a Pull Request against the parent BU's repository (e.g., bu-data-80r13).
  2. The PR adds a new Tenant module call to the BU's terraform/main.tf.
  3. Once the PR is reviewed and merged, Terraform Cloud applies the changes.
  4. The apply creates the GCP projects, GitHub repo, WIF configurations, and TFC workspaces.
  5. The team can then start using their new repository to deploy workloads.

What Gets Created

GCP Projects

A separate GCP project is created for each environment, inside the parent BU's environment folders:

Each project is:

  • Linked to the appropriate billing account
  • Attached to the Shared VPC (base or restricted) as a service project
  • Protected by the environment's organization policies
  • Pre-configured with the necessary APIs enabled

GitHub Repository

A repository named {bu}-{tenant}-{suffix} is created from the foundations-template:

{bu}-{tenant}-{suffix}/
├── terraform/
│ ├── configs/
│ │ ├── dev/
│ │ │ └── resources.yaml # Dev environment config
│ │ ├── non-prod/
│ │ │ └── resources.yaml # Non-prod environment config
│ │ └── prod/
│ │ └── resources.yaml # Prod environment config
│ ├── main.tf # Single module call to tenant-infra-factory
│ ├── variables.tf
│ ├── providers.tf
│ └── backend.tf
├── .github/
│ └── workflows/ # CI/CD using reusable workflows
├── catalog-info.yaml # Backstage registration
└── backstage-manifest.json # Backstage metadata

YAML-Driven Configuration

The tenant repository uses a YAML-driven approach to infrastructure. Instead of writing Terraform directly, teams define their resources in YAML files under terraform/configs/{env}/:

# terraform/configs/dev/resources.yaml
cloud_run:
- name: my-service
image: northamerica-northeast1-docker.pkg.dev/my-project/my-repo/my-image:latest
port: 8080
memory: 512Mi
cpu: 1
min_instances: 0
max_instances: 3

gcs:
- name: my-bucket
location: northamerica-northeast1
storage_class: STANDARD

bigquery:
- name: my_dataset
location: northamerica-northeast1

labels:
team: data
cost-center: engineering

The main.tf contains a single module call to tenant-infra-factory which reads these YAML configs and provisions the resources.

Supported Resources

ResourceYAML KeyDescription
Cloud Runcloud_runManaged serverless containers
GCS BucketsgcsCloud Storage buckets
BigQuerybigqueryBigQuery datasets
LabelslabelsResource labels applied to all resources

Terraform Cloud Workspaces

Per-environment TFC workspaces are created for the tenant:

WorkspaceEnvironmentExample
{bu}-{tenant}-d-{suffix}Developmentdata-governance-d-f6y39
{bu}-{tenant}-np-{suffix}Non-productiondata-governance-np-f6y39
{bu}-{tenant}-p-{suffix}Productiondata-governance-p-f6y39

Each workspace is connected to the tenant's GitHub repository and deploys the corresponding environment configuration.

Workload Identity Federation (WIF)

Each environment gets its own WIF configuration:

This ensures that a GitHub Actions workflow running against the dev branch can only access dev resources — it cannot impersonate the production service account.

Branching Models

Tenants support two branching models for CI/CD:

Trunk-Based (Push/Tag)

  • All development happens on main
  • Pushes to main deploy to dev/non-prod
  • Git tags (e.g., v1.0.0) trigger production deployments
  • Recommended for most teams

Environment-Based

  • Separate branches per environment: development, non-production, production
  • Merging to a branch deploys to that environment
  • Useful when teams need independent environment control
  • Mirrors the approach used by the foundation stages (e.g., gcp-foundations-envs)

Budget Configuration

Tenants support optional budget alerts:

budget_config = {
enabled = true
amount = 1000 # Monthly budget in USD
alerts = [0.5, 0.8, 1.0] # Alert at 50%, 80%, 100%

notification_channels = [
"projects/my-project/notificationChannels/12345"
]
}

Budget alerts are configured per environment, allowing different thresholds for dev (lower) and production (higher).

Google Artifact Registry (GAR)

If the parent BU has GAR enabled, tenant-level standard repositories are created per environment and per artifact type. These feed into the BU-level virtual repositories:

Tenant standard repo (per env)
└── BU standard repo (shared)
└── BU virtual repo (aggregated)

This tiered architecture allows teams to publish artifacts at the tenant level while consumers can pull from a single BU-level virtual endpoint.

Example: Creating the "governance" Tenant

Here is what the module call looks like in the bu-data-80r13 repository:

module "tenant_governance" {
source = "app.terraform.io/Badal_devex/create-tenant/platform"

tenant_name = "governance"
business_unit_name = "data"
business_unit_id = module.bu_data.business_unit_id

environments = ["development", "non-production", "production"]

github_org = "badal-io"
github_team = "data-team"

budget_config = {
enabled = true
amount = 500
alerts = [0.5, 0.8, 1.0]
}
}

Troubleshooting

IssueResolution
Tenant projects not appearingCheck the BU workspace in TFC. The tenant module runs inside the BU workspace, not a separate one.
WIF authentication fails in CIVerify the GitHub repo name matches the WIF pool's attribute condition. The subject must match the repo and branch.
YAML config not appliedEnsure the YAML file path matches terraform/configs/{env}/*.yaml and re-run the TFC workspace for that environment.
Budget alerts not firingVerify notification channels exist in the project and the budget amount is set correctly. Budget data can take 24-48 hours to appear.

Next Steps