Provisioning at Scale: Terraform Operating Model for 40 Projects
Helios Logistics has 40 Google Cloud projects in 3 folders, 1,200 Compute Engine VMs, 300 Cloud Run services, and 6 product teams. Only two teams use Terraform; the rest provision through the console and scripts. A recent audit found 30% configuration drift, 18 service account keys older than 90 days, and an average lead time of 9 days to create a compliant project. Security wants organization policies enforced and a full audit trail, while teams want self-service. There are 14 Shared VPC host projects and 22 service projects. The company uses GitHub Enterprise and Cloud Build. You are asked to design a provisioning operating model that reduces lead time to under one day without giving teams owner on the organization. Budget allows a small platform team of four engineers and no new commercial tools beyond Google Cloud.
1. Design the Terraform repository, module, and state model for 40 projects and six teams. What belongs in shared modules versus team-owned stacks?
Read the worked answer
Separate blast radius from reuse
Use a platform repository for shared modules and organization-level configuration, and allow team repositories to consume versioned modules. Do not put all 40 projects in one state file: a bad plan could affect every project and every team would block on the same lock.
Suggested structure
modules/project-factory: project creation, labels, billing link, default service accounts, audit logging, org policy attachments.modules/network: Shared VPC host/service attachment, subnets, firewall policy, private service access.modules/iam-baseline: groups, predefined roles, IAM conditions, deny policies, service account impersonation.modules/workload-baseline: Cloud Run service, Artifact Registry, Cloud Build trigger, alert policies.envs/org: folder and org policy assignments.envs/folders/<folder>: folder-level IAM and policy.envs/projects/<project>: one state per project or per small project group.
State and ownership
Store state in a versioned GCS bucket with CMEK, access logging, and object versioning. Use separate prefixes per environment and per project group. The platform team owns modules and org-level state; product teams own their project stacks and can propose module changes through pull requests. Pin module versions by Git tag. Shared VPC host configuration stays with the platform team; service projects consume outputs rather than editing the host.
Why this shape
It gives teams self-service through a project-factory module while keeping organization policy, Shared VPC, and audit logging centralized. It also limits the damage of a bad apply and makes drift detection practical.
2. Design the provisioning workflow from GitHub pull request to production apply, including policy gates and approvals. How do you keep lead time under one day?
Read the worked answer
Pull-request workflow
- Engineer opens a PR against a team stack or a project-factory YAML file.
- Cloud Build trigger runs
terraform fmt -check,terraform validate, andterraform plan -out=tfplanusing a deploy service account scoped to the target folder or project. - Policy checks run in the same pipeline: org policy simulation where available, OPA/Conftest rules for required labels, approved regions, public IP denial, and mandatory logging. Fail the PR on violations.
- Plan output is attached to the PR for review. A platform reviewer approves only high-risk changes such as org policy, Shared VPC, or IAM at folder level. Standard project changes can use automated approval if they match the template.
- On merge, the same saved plan is applied, not a newly generated plan. This prevents unreviewed drift between plan and apply.
Identity and approvals
Use Workload Identity Federation from GitHub to Cloud Build or directly to a deploy service account. No service account keys. The deploy account should be able to impersonate narrower per-folder deploy accounts, not own the organization. Cloud Build approvals or GitHub environment protection provide the human gate.
Keeping lead time low
Pre-approve the common path: a new project from the factory module with standard labels, logging, and a Shared VPC attachment. Automate policy checks and plan generation. Reserve manual review for exceptions. Run scheduled terraform plan drift detection so teams fix drift before it becomes a nine-day ticket.
3. How would you bring the existing console-created resources and the 18 old service account keys under control without freezing all teams?
Read the worked answer
Inventory before import
Start with Cloud Asset Inventory and labels to identify unmanaged resources. Prioritize by risk: public buckets, firewall rules, overly broad IAM, and service account keys. Do not import everything at once; group resources by team and environment.
Import safely
Use Terraform import blocks with terraform plan -generate-config-out where supported, then review and refactor the generated configuration into modules. For resources that cannot be imported cleanly, recreate them through the module and migrate traffic or data with a tested plan. Keep a read-only audit period where the pipeline reports drift but does not auto-remediate. Once a resource is owned, enable scheduled drift detection and require changes through PRs.
Service account keys
Treat the 18 keys as an incident, not a cleanup ticket. Inventory owners and usage, create replacement credentials using Workload Identity Federation or service account impersonation, deploy the change, verify, then disable old keys before deleting them. Apply an org policy to disable new key creation except for a break-glass project. Never store keys in Terraform state or Git.
Avoid a freeze
Publish a migration schedule by team. Offer a fast path for new projects through the factory module immediately, and a 30-day window for existing resources with platform support. Teams that need an exception can use a time-bound, reviewed exception with compensating controls. This keeps delivery moving while reducing the unmanaged surface.