Best practices for general style and structure

This document provides basic style and structure recommendations for yourTerraform configurations. These recommendations apply to reusable Terraformmodules and to root configurations.

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

Follow a standard module structure

  • Terraform modules must follow thestandard module structure.
  • Start every module with amain.tf file, where resources are located bydefault.
  • In every module, include aREADME.md file in Markdown format. In theREADME.md file, include basic documentation about the module.
  • Place examples in anexamples/ folder, with a separate subdirectory foreach example. For each example, include a detailedREADME.md file.
  • Create logical groupings of resources with their own files anddescriptive names, such asnetwork.tf,instances.tf, orloadbalancer.tf.
    • Avoid giving every resource its own file. Group resources bytheir shared purpose. For example, combinegoogle_dns_managed_zoneandgoogle_dns_record_set indns.tf.
  • In the module's root directory, include only Terraform(*.tf) and repository metadata files (such asREADME.md andCHANGELOG.md).
  • Place any additional documentation in adocs/ subdirectory.

Adopt a naming convention

  • Name all configuration objects using underscores to delimit multiplewords. This practice ensures consistency with the naming convention forresource types, data source types, and other predefined values. Thisconvention does not apply to namearguments.

    Recommended:

    resource "google_compute_instance""web_server" {  name = "web-server"}

    Not recommended:

    resource "google_compute_instance""web-server" {  name = "web-server"}
  • To simplify references to a resource that is the only one of its type(for example, a single load balancer for an entire module), name theresourcemain.

    • It takes extra mental work to remembersome_google_resource.my_special_resource.id versussome_google_resource.main.id.
  • To differentiate resources of the same type from each other (for example,primary andsecondary), provide meaningful resource names.

  • Make resource names singular.

  • In the resource name, don't repeat the resource type.For example:

    Recommended:

    resource "google_compute_global_address" "main" { ... }

    Not recommended:

    resource "google_compute_global_address" "main_global_address" { … }

Use variables carefully

  • Declare all variables invariables.tf.
  • Give variables descriptive names that are relevant to their usage orpurpose:
    • Inputs, local variables, and outputs representing numericvalues—such as disk sizes or RAM size—must be named withunits (such asram_size_gb). Google Cloud APIs don't have standardunits, so naming variables with units makes the expectedinput unit clear for configuration maintainers.
    • For units of storage, use binary unit prefixes (powers of 1024)—kibi,mebi,gibi. For all other units of measurement, use decimalunit prefixes (powers of 1000)—kilo,mega,giga. This usagematches the usage within Google Cloud.
    • To simplify conditional logic, give boolean variables positive names—forexample,enable_external_access.
  • Variables must have descriptions. Descriptions are automaticallyincluded in a published module's auto-generated documentation.Descriptions add additional context for new developers that descriptivenames cannot provide.
  • Give variables defined types.
  • When appropriate, provide default values:
    • For variables that have environment-independent values (such as disksize), provide default values.
    • For variables that have environment-specific values (such asproject_id), don't provide default values. This way, the calling modulemust provide meaningful values.
  • Use empty defaults for variables (like empty strings or lists) only whenleaving the variable empty is a valid preference that the underlying APIsdon't reject.
  • Be judicious in your use of variables. Only parameterize values thatmust vary for each instance or environment. When deciding whether to exposea variable, ensure that you have a concrete use case for changing thatvariable. If there's only a small chance that a variable might be needed,don't expose it.
    • Adding a variable with a default value is backwards-compatible.
    • Removing a variable is backwards-incompatible.
    • In cases where a literal is reused in multiple places, you can use alocal valuewithout exposing it as a variable.

Expose outputs

  • Organize all outputs in anoutputs.tf file.
  • Provide meaningful descriptions for all outputs.
  • Document output descriptions in theREADME.md file. Auto-generatedescriptions on commit with tools liketerraform-docs.
  • Output all useful values that root modules might need to refer to or share.Especially for open source or heavily used modules, expose all outputs thathave potential for consumption.
  • Don't pass outputs directly through input variables, because doing soprevents them from being properly added to the dependency graph. To ensurethatimplicit dependenciesare created, make sure that outputs reference attributes from resources.Instead of referencing an input variable for an instance directly, passthe attribute through as shown here:

    Recommended:

    output "name" {  description = "Name of instance"  value       = google_compute_instance.main.name}

    Not recommended:

    output "name" {  description = "Name of instance"  value       = var.name}

Use data sources

  • Putdata sourcesnext to the resources that reference them. For example, if you are fetchingan image to be used in launching an instance, place it alongside theinstance instead of collecting data resources in their own file.
  • If the number of data sources becomes large, consider moving them to adedicateddata.tf file.
  • To fetch data relative to the current environment, use variable or resourceinterpolation.

Limit the use of custom scripts

  • Use scripts only when necessary. The state of resources createdthrough scripts is not accounted for or managed by Terraform.
    • Avoid custom scripts, if possible. Use them only when Terraformresources don't support the desired behavior.
    • Any custom scripts used must have a clearly documented reason forexisting and ideally a deprecation plan.
  • Terraform can call custom scripts through provisioners, including thelocal-exec provisioner.
  • Put custom scripts called by Terraform in ascripts/ directory.

Include helper scripts in a separate directory

  • Organize helper scripts that aren't called by Terraform in ahelpers/ directory.
  • Document helper scripts in theREADME.md file with explanations andexample invocations.
  • If helper scripts accept arguments, provide argument-checking and--help output.

Put static files in a separate directory

  • Static files that Terraform references but doesn't execute (such asstartup scripts loaded onto Compute Engine instances) must be organizedinto afiles/ directory.
  • Place lengthy HereDocs in external files, separate from their HCL.Reference them with thefile() function.
  • For files that are read in by using the Terraformtemplatefile function,use the file extension.tftpl.
    • Templates must be placed in atemplates/ directory.

Protect stateful resources

For stateful resources, such as databases, ensure thatdeletion protectionis enabled. For example:

resource"google_sql_database_instance""main"{name="primary-instance"settings{tier="D0"}lifecycle{prevent_destroy=true}}

Use built-in formatting

All Terraform files must conform to the standards ofterraform fmt.

Limit the complexity of expressions

  • Limit the complexity of any individual interpolated expressions. Ifmany functions are needed in a single expression, consider splitting it outinto multiple expressions by usinglocal values.
  • Never have more than one ternary operation in a single line. Instead,use multiple local values to build up the logic.

Usecount for conditional values

To instantiate a resource conditionally, use thecount meta-argument.For example:

variable"readers"{description="..."type=listdefault=[]}resource"resource_type""reference_name"{//Donotcreatethisresourceifthelistofreadersisempty.count=length(var.readers)==0?0:1...}

Be sparing when using user-specified variables to set thecount variable forresources. If a resource attribute is provided for such a variable (likeproject_id) and that resource does not yet exist, Terraform can'tgenerate a plan. Instead, Terraform reports the errorvalue of count cannot be computed.In such cases, use a separateenable_x variable to compute theconditional logic.

Usefor_each for iterated resources

If you want to create multiple copies of a resource based on an input resource,use thefor_eachmeta-argument.

Publish modules to a registry

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-10-24 UTC.