How the two-repository GCP Terragrunt boilerplate keeps live infrastructure separate from reusable modules
- Live configuration starts in the Terragrunt GCP projects repository. This is the repo an operator should open during a review, rollout, or incident to answer practical questions: what is deployed in dev, staging, and production; which Google Cloud projects are in scope; which inputs vary by environment; where remote state is stored; and how Terragrunt dependencies shape apply order. Keep this layer about composition, ownership, and environment wiring. If resource implementation details start piling up here, the live repo quietly becomes an unversioned module library, and every production change becomes harder to review safely.
- Reusable infrastructure belongs in the Terraform GCP modules repository. Treat it as the shared library for GCP building blocks: VPC and subnet patterns, IAM bindings, service accounts, project services, logging defaults, and other primitives that should behave consistently across environments. The point is not abstraction for its own sake; it is controlled reuse. A module can be reviewed, tested, tagged, and then consumed deliberately by live configurations. That is much easier to operate than copying resource blocks into every project directory and discovering six months later that production, staging, and dev all evolved in slightly different ways.
โ
Terraform failure modes this GCP Terragrunt boilerplate is designed to prevent
This boilerplate exists because the most expensive Terraform problems in platform teams rarely begin with one obviously wrong resource block. They begin with the operating model: unclear ownership, inconsistent directory layouts, duplicated provider configuration, postponed backend decisions, and a promotion path from development to staging to production that depends on tribal knowledge. By the time the problem shows up, it is usually not a neat plan-time error. It is a tangled incident involving state access, IAM permissions, missing APIs, dependency ordering, and pressure to ship a fix without making the blast radius worse.
Terraform is rarely the hard part on day one. A team can get a GCP project, a network, and a compute workload deployed quickly, then feel immediate pressure to reuse that first stack for every service, environment, and region. That is where the damage starts: the proof of concept quietly becomes the operating model. Without a structure the team can maintain, the same problems keep showing up in reviews, handoffs, and production changes:
- Provider, backend, and remote-state settings get copied across stacks, so small configuration drift becomes difficult to catch in review.
- Environment-specific changes are patched directly into Terraform files instead of being passed as explicit, reviewable inputs.
- State moves feel risky because stack ownership, resource boundaries, and blast radius were not agreed before resources went live.
- Shared modules exist, but ownership, versioning, compatibility expectations, and release flow are unclear.
- Pull requests grow too large because module changes, environment wiring, IAM updates, and rollout sequencing are bundled together.
- Apply order is discovered through failures instead of being declared through Terragrunt dependencies.
- Writing all of the Terraform code in one main.tf file
- Copy-pasting resources manually
- Copy-pasting configuration throughout the codebase
- No state separation and environment awareness
This is why they regretted the above:
- One
terraform applycould ruin an entire environment - Resources modifications required changes in multiple locations
- Configuration modifications required changes in multiple locations
- Accidentally deploying the wrong resources to the wrong environment
And so, I built this boilerplate for our clients (and you) to minimize regrets.
The focus of this boilerplate is managing GCP resources.
โ
Is this boilerplate for you?
If you are a CTO, a DevOps lead, or a platform engineer starting a new GCP project, the pressure arrives early: ship the infrastructure quickly, but do not leave behind a repository layout that becomes painful after the second environment. A practical Terraform and Terragrunt boilerplate gives the team a clean baseline for projects, regions, modules, remote state, and shared configuration. I did not find a GCP deployment example with a structure I would reuse in production, so I built one, opened it up, and will walk through the decisions behind it here.
โ
What should you expect from this guide?
By the end of this guide, you should understand how to organize Terraform and Terragrunt for GCP in a way that supports real environments instead of a one-off demo. You will also run a small deployment flow so you can see how the repository layout, shared configuration, module calls, and outputs fit together end to end.
โ
What shouldn't you expect from this guide?
An exhaustive library of modules for every resource in GCP. We kept the boilerplate minimal, so that you can utilize it for your needs.
You can fairly easily utilize existing modules you created or found.
โ
Getting Started
To begin, clone the essential repositories:
โ
Primary Repository
Clone the terragrunt-gcp-projects repository to get started.
git clone git@github.com:MeteorOps/terragrunt-gcp-projects.git
โ
Modules Repository (Optional)
For the modules used, clone the terraform-gcp-modules repository.
git clone git@github.com:MeteorOps/terraform-gcp-modules.git
โ
Repository Structure Explained
The repository is organized as a hierarchy of reusable modules, live environment configuration, and shared Terragrunt settings. That separation matters once you have more than one project, region, or environment, because it keeps the intent of each layer visible during review and rollout. In practice, this structure gives you several benefits:
- Hierarchical configuration: The configuration at each level cascades through the folders under it
- State separation: The terraform state is saved per folder in a different path in a bucket, limiting the impact radius of changes
- Dynamic-level of deployment: The deeper into the folder you go, the more specific resources you affect with one deployment
โ
project
โ _global
โ region
โ _global
โ environment
โ resource
โ
Creating and using root (project) level variables
Once you manage more than one GCP project, environment, or region, common module inputs become one of the first sources of drift. Project IDs, default regions, labels, naming prefixes, and other shared values should not be copied into every stack by hand. Keep those defaults in the root terragrunt.hcl and let child terragrunt.hcl files inherit them, overriding only the values that genuinely differ for that environment or region.
โ
Deployment Using Terragrunt
Prerequisites
- Install Terraform version
0.12.6or newer and Terragrunt versionv0.25.1or newer. - Fill in your GCP Project ID in
my-project/project.hcl. - Make sure gcloud CLI is installed and you are authenticated, otherwise run
gcloud auth login.
Module Deployment
To deploy a single module:
cdinto the module's folder (e.g.cd my-project/us-central1/rnd-1/vpc).- Run
terragrunt planto see the changes you're about to apply. - If the plan looks good, run
terragrunt apply.
Environment Deployment
To deploy all modules within an environment:
cdinto the environment folder (e.g.cd my-project/us-central1/rnd-1).- Run
terragrunt run-all planto see all the changes you're about to apply. - If the plan looks good, run
terragrunt run-all apply.
Testing Deployed Infrastructure
After deployment, keep module outputs intentionally small. Export the values an operator needs to verify the stack, or the values another stack must consume through Terragrunt dependencies. Do not expose every resource attribute just because Terraform makes it easy; noisy outputs turn dependency contracts into guesswork and make refactors harder. For this example, the useful contract is simple: the application IP address.
Outputs:
ip = "35.240.219.84"
After the deployment has settled, use the exported ip value as a quick smoke test from your browser or with curl:
curl --fail --show-error http://35.240.219.84
# Output: Let MeteorOps know if the boilerplate needs any improvement!
Clean-Up Process
To remove all deployed modules within an environment:
cdinto the environment folder (e.g.cd my-project/us-central1/rnd-1).- Run
terragrunt run-all plan -destroyto see all the destroy changes you're about to apply. - If the plan looks good, run
terragrunt run-all destroy.
โ
Conclusion
This guide walks you through leveraging best practices for setting up and managing Terraform repositories for GCP with Terragrunt. These methodologies are designed to be straightforward, efficient, and easily adaptable to future projects or company needs.




