Why this GCP Terragrunt starter separates live infrastructure from reusable modules
- Live configuration starts in the Terragrunt GCP projects repository. Treat this repository as the operational map of your Google Cloud estate, not as a dumping ground for one-off Terraform resource definitions. During a review, rollout, or incident, an operator should be able to open it and quickly answer practical questions: what is deployed in development, staging, and production; which Google Cloud projects, folders, regions, and services are in scope; which inputs differ by environment; where remote state is stored; and how Terragrunt dependencies shape plan and apply order. Keep this layer focused on composition, ownership, and environment wiring. It should show which approved building blocks are used for a specific project, region, team, or environment, while leaving the implementation of those building blocks to the module layer. A good live repository makes promotion paths obvious: development can pin one module version, staging can validate the next, and production can move only when the plan is understood. When resource details start accumulating here, the boundary has slipped. The live layer becomes an accidental module library, and every production change becomes harder to review, promote, audit, and roll back because environment wiring, rollout sequencing, and resource design are tangled into the same pull request.
- Reusable infrastructure belongs in the Terraform GCP modules repository. Treat this repository as the shared library for Google Cloud building blocks: VPC and subnet patterns, IAM bindings, service accounts, project services, logging defaults, and other primitives that should behave consistently across environments. The goal is not abstraction for its own sake. The goal is controlled reuse behind a contract that platform maintainers and service teams can both reason about. A useful module exposes the inputs that should vary, keeps opinionated defaults and implementation details consistent, and makes breaking changes visible before they reach production. For example, a network module should let environments choose CIDR ranges, regions, and labels, but it should not require every application team to reimplement the same routing, logging, or subnet conventions. That module can be changed in isolation, tested with representative inputs, tagged or pinned by the live configuration, and promoted deliberately through environments. This is far easier to operate than copying resource blocks into every project directory and later discovering that production, staging, and development have drifted into slightly different versions of the same network, IAM, or project-service pattern.
โ
Terraform failure modes this GCP Terragrunt boilerplate helps prevent
This boilerplate exists because the most expensive Terraform problems in platform teams rarely start with one obviously broken resource block. They usually start with the operating model: unclear ownership, inconsistent directory layouts, duplicated provider configuration, backend decisions made after resources already exist, undocumented state boundaries, and a development-to-staging-to-production promotion path that only a few engineers can explain. By the time the weakness becomes visible, it is often no longer a tidy plan-time error. It is an incident-shaped problem involving state access, IAM permissions, disabled Google Cloud APIs, dependency ordering, provider configuration, and pressure to apply a fix without widening the blast radius. A familiar example is a production deploy blocked because a dependent project service was never enabled in the right project, the service account lacks the expected role, and the stack that owns the fix is not obvious. The starter layout pushes those choices into the open early, while they are still cheap to review, document, and automate.
Terraform is rarely the hard part on day one. A team can create a Google Cloud project, enable a few APIs, deploy a network, and run an initial workload quickly. The pressure arrives after that first success: reuse the stack for another service, another environment, another region, or another team. If the foundation is still shaped like a proof of concept, it can quietly become the production operating model before anyone has agreed on ownership, state boundaries, naming conventions, dependency rules, or promotion gates. Without a structure the team can maintain, the same problems keep appearing in reviews, handoffs, CI runs, and production changes:
- Provider, backend, and remote-state settings are copied across stacks, so small configuration drift is hard to spot in pull requests.
- Environment-specific differences are patched directly into Terraform resources instead of being passed as explicit, reviewable inputs.
- State changes feel risky because stack ownership, resource boundaries, import strategy, 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 become too large because module code, environment wiring, IAM updates, and rollout sequencing are bundled into one change.
- Apply order is discovered through failed runs instead of being declared through Terragrunt dependencies and reviewed as part of the design.
- Google Cloud project services, service accounts, and IAM bindings are enabled or changed ad hoc, slowing least-privilege review, audit trails, and incident response.
- Teams rely on tribal knowledge for which stack to plan first, which project owns shared resources, and which changes are safe to promote independently.
- CI jobs cannot reliably run targeted plans because stack boundaries and dependency relationships are implicit rather than encoded in the repository.
- Emergency fixes bypass the normal path because no one trusts the plan output enough to make a small, scoped change under pressure.
- 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
That is the reason for this boilerplate: make the first Terraform and Terragrunt decisions boring, reviewable, and easy to change before they harden into platform debt.
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: The sample workload is reachable.
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.




