Terraform Starter Boilerplate for GCP using Terragrunt
DevOps Engineering

Terraform Starter Boilerplate for GCP using Terragrunt

A Terragrunt boilerplate to minimize regrets on GCP.

Arthur Azrieli

6 min read

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 usually works on day one. The problems start when the first working stack becomes the pattern for every new project, region, and environment. Without a structure the team can maintain, the same issues show up in reviews, handoffs, and production changes:

  • Provider, backend, and remote-state configuration is copied between stacks until small drift becomes hard to spot in review.
  • Environment differences are patched directly in files instead of being expressed as explicit, reviewable inputs.
  • State migrations feel dangerous because stack ownership and resource boundaries were never agreed up front.
  • Shared modules exist, but nobody is clear on ownership, versioning, compatibility, or how changes are released.
  • Pull requests become too large because module code, environment wiring, IAM updates, and rollout sequencing all change together.
  • Dependency order is learned from failed applies instead of being declared in Terragrunt configuration.
A starter boilerplate does not remove the need for engineering judgment. It gives the team a default shape: smaller changes, clearer ownership, easier promotion between environments, and fewer surprises when the infrastructure grows beyond the demo.

  1. Writing all of the Terraform code in one main.tf file
  2. Copy-pasting resources manually
  3. Copy-pasting configuration throughout the codebase
  4. No state separation and environment awareness

This is why they regretted the above:

  1. One terraform apply could ruin an entire environment
  2. Resources modifications required changes in multiple locations
  3. Configuration modifications required changes in multiple locations
  4. 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:

  1. Hierarchical configuration: The configuration at each level cascades through the folders under it
  2. State separation: The terraform state is saved per folder in a different path in a bucket, limiting the impact radius of changes
  3. 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

  1. Install Terraform version 0.12.6 or newer and Terragrunt version v0.25.1 or newer.
  2. Fill in your GCP Project ID in my-project/project.hcl.
  3. Make sure gcloud CLI is installed and you are authenticated, otherwise run gcloud auth login.

Module Deployment

To deploy a single module:

  1. cd into the module's folder (e.g. cd my-project/us-central1/rnd-1/vpc).
  2. Run terragrunt plan to see the changes you're about to apply.
  3. If the plan looks good, run terragrunt apply.

Environment Deployment

To deploy all modules within an environment:

  1. cd into the environment folder (e.g. cd my-project/us-central1/rnd-1).
  2. Run terragrunt run-all plan to see all the changes you're about to apply.
  3. If the plan looks good, run terragrunt run-all apply.

Testing Deployed Infrastructure

After deployment, each module should expose only the values an operator or downstream stack actually needs. Avoid turning outputs into a dumping ground for every resource attribute; that makes dependencies harder to reason about. In this example, the useful output is 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 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:

  1. cd into the environment folder (e.g. cd my-project/us-central1/rnd-1).
  2. Run terragrunt run-all plan -destroy to see all the destroy changes you're about to apply.
  3. 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.