Best practices for reusable modules

This document provides guidelines and recommendations to consider when usingreusable Terraform modules.

This guide is not an introduction to Terraform. For an introduction to usingTerraform with Google Cloud, seeGet started with Terraform.

Activate required APIs in modules

Terraform modules can activate any required services by using thegoogle_project_service resource or theproject_services module.Including API activation makes demonstrations easier.

  • If API activation is included in a module, then the API activationmustbe disableable by exposing anenable_apis variable that defaults totrue.
  • If API activation is included in a module, then the API activationmustsetdisable_services_on_destroy tofalse, because this attribute cancause issues when working with multiple instances of the module.

    For example:

    module"project-services"{source="terraform-google-modules/project-factory/google//modules/project_services"version="~> 12.0"project_id=var.project_idenable_apis=var.enable_apisactivate_apis=["compute.googleapis.com","pubsub.googleapis.com",]disable_services_on_destroy=false}

Include an owners file

For all shared modules, include anOWNERSfile (orCODEOWNERSon GitHub), documenting who is responsible for the module. Before any pullrequest is merged, an owner should approve it.

Release tagged versions

Sometimes modules require breaking changes and you need to communicate theeffects to users so that they can pin their configurations to a specificversion.

Make sure that shared modules followSemVer v2.0.0when new versions are tagged or released.

When referencing a module, use aversion constraintto pin to themajor version. For example:

module"gke"{source="terraform-google-modules/kubernetes-engine/google"version="~> 20.0"}

Don't configure providers or backends

Shared modulesmust not configure providers or backends.Instead, configure providers and backends in root modules.

For shared modules, define the minimum required provider versions in arequired_providersblock, as follows:

terraform{required_providers{google={source="hashicorp/google"version=">= 4.0.0"}}}

Unless proven otherwise, assume that new provider versions will work.

Expose labels as a variable

Allow flexibility in the labeling of resources through the module's interface.Consider providing alabels variable with a default value of an empty map, asfollows:

variable"labels"{description="A map of labels to apply to contained resources."default={}type="map"}

Expose outputs for all resources

Variables and outputs let you infer dependencies between modules and resources.Without any outputs, users cannot properly order your module in relation totheir Terraform configurations.

For every resource defined in a shared module, include at least one output thatreferences the resource.

Use inline submodules for complex logic

  • Inline modules let you organize complex Terraform modules intosmaller units and de-duplicate common resources.
  • Place inline modules inmodules/$modulename.
  • Treat inline modules as private, not to be used by outside modules,unless the shared module's documentation specifically states otherwise.
  • Terraform doesn't track refactored resources. If you start with severalresources in the top-level module and then push them into submodules,Terraform tries to recreate all refactored resources. To mitigate thisbehavior, usemovedblocks when refactoring.
  • Outputs defined by internal modules aren't automatically exposed. To shareoutputs from internal modules, re-export them.

What's next

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2025-12-15 UTC.