Skip to main content

Resources

What are resources?

Exam guide§2.4

A resource is an infrastructure element you configure with Terraform - a Compute Engine instance, a VPC, a Cloud Storage bucket, a firewall rule. Real-world infrastructure has a diverse set of resources and resource types, and Terraform deploys each one by calling the underlying API of that Google Cloud service.

TerraformResourcesCompute EngineFirewall RulesCloud VPNVirtual Private Cloud (VPC)Cloud Load BalancingCloud Router
Resources are the infrastructure elements Terraform manages - Compute Engine, firewall rules, Cloud VPN, VPC, Cloud Load Balancing, Cloud Router - each deployed through the underlying API of that Google Cloud service.
FactsResource types you will meet
  • Compute - instances, instance templates, instance groups
  • Networking - VPC networks, firewall rules, VPN tunnels, Cloud Routers
  • Traffic - load balancers
  • Storage - Cloud Storage buckets

The Terms and Concepts page covers the anatomy of a single resource block (keyword, type, name, arguments). This page builds on that: multiple resources in one file, referencing one resource from another, the rules that make a block valid, the meta-arguments shared by every resource type, and how Terraform orders resources through dependencies.

Multiple resources in one configuration

A single .tf file can hold multiple resources of the same or different types, and they can even span multiple providers. The recommendation is to group similar resource types in a directory and declare them in main.tf (the root configuration).

Here main.tf declares a VPC network and a subnet in the same file:

resource "google_compute_network" "vpc_network" {
name = "vpc-network" # required
project = "<project_id>"
auto_create_subnetworks = false
mtu = 1460
}
 
resource "google_compute_subnetwork" "subnetwork-ipv6" {
name = "ipv6-test-subnetwork" # required
ip_cidr_range = "10.0.0.0/22" # required
network = google_compute_network.vpc_network.id # required
region = "us-west2"
}
GotchaWhich arguments exist depends on the resource type

A google_compute_network takes arguments like name, project, and auto_create_subnetworks; a google_compute_subnetwork takes name, ip_cidr_range, and network. The set of valid arguments - and which are required vs optional - is fixed by the resource type, not chosen by you.

Referring to a resource attribute

When one resource needs a value produced by another, reference it with the format:

<resource_type>.<resource_name>.<attribute>

In the example above, the subnet needs the network ID of the VPC it belongs to. That network ID is a computed attribute of the google_compute_network block - it is not known until the network is created. The subnet reaches it with:

network = google_compute_network.vpc_network.id
GotchaAttribute references work only within the same root configuration

This <type>.<name>.<attribute> reference resolves only when both resources are defined in the same root configuration. A computed attribute (like a network ID or a bucket URL) is generated when the resource is created, so Terraform wires the dependency order for you - it builds the network first, then feeds its ID into the subnet.

Considerations for defining a resource block

Exam guide§2.4
GotchaThe resource name must be unique within the module

A declared resource is identified by its type and name together, so two blocks of the same type cannot share a name in the same configuration - Terraform flags the second as a duplicate and the plan fails.

GotchaThe resource type is a provider keyword - never user-defined

The type is a keyword owned by the provider and must match the term in the Terraform Registry (a Cloud Storage bucket is google_storage_bucket, not cloud_storage_bucket). A made-up type has no schema, so terraform plan / terraform apply rejects even its arguments:

Terminal
Error: Unsupported argument
on main.tf line 4:
4: location = "US"
An argument named "location" is not expected here.
A made-up resource type has no schema, so the plan rejects its arguments
GotchaEvery required argument must be defined inside the braces

All configuration arguments live in the resource block body (between the curly braces). A configuration will not pass the plan and apply phases until every required argument is present - here name is missing:

Terminal
Error: Missing required argument
on main.tf line 9, in resource "google_storage_bucket" "dev_bucket":
9: resource "google_storage_bucket" "dev_bucket" {
The argument "name" is required, but no definition was found.
The plan fails until every required argument is present - here name is missing

Meta-arguments

Exam guide§2.4

The Terraform language defines several meta-arguments that can be used with any resource type to change the behavior of resources. They sit inside the resource block alongside the resource-specific arguments.

Meta-argumentWhat it does
countCreate multiple instances according to the value assigned to the count.
for_eachCreate multiple resource instances as per a set of strings (or a map).
depends_onSpecify an explicit dependency.
lifecycleDefine the life cycle of a resource.
providerSelect a non-default provider configuration.
Gotcha`lifecycle` protects resources from replacement

With the lifecycle argument you can prevent destruction of a resource for compliance purposes, and create a resource before destroying the one it replaces (create_before_destroy). The create-before-destroy approach is often used for high availability.

Gotcha`provider` selects among multiple configurations

You can declare multiple configurations for the same provider (including a default). provider on a resource picks a non-default one - for example, to create resources in a second region or project.

This section covers count and for_each in detail - the two meta-arguments that replace copy-pasted resource blocks with a single definition. The depends_on meta-argument is covered under Resource dependencies below.

one resource blockcount = 3 / for_each = …expands toinstance [0] → dev_VM1 / dev-us-central1-ainstance [1] → dev_VM2 / dev-asia-east1-binstance [2] → dev_VM3 / dev-europe-west4-a
count and for_each turn one resource block into many instances - by index (count) or by set member (for_each)

count: multiple resources of the same type

Suppose you must deploy several near-identical VM instances. Writing one google_compute_instance block per VM is redundant:

resource "google_compute_instance" "dev_VM1" {
name = "dev_VM1"
…}
 
resource "google_compute_instance" "dev_VM2" {
name = "dev_VM2"
…}
 
resource "google_compute_instance" "dev_VM3" {
name = "dev_VM3"
…}

Add the count argument at the top of the block instead. count tells Terraform to create that many instances of the same kind:

resource "google_compute_instance" "Dev_VM" {
count = 3
name = "dev_VM${count.index + 1}"
#other required arguments
}
NumbersHow `count.index` works
  • count.index is the index of the current count loop.
  • It starts at 0 and increments by 1 for each resource.
  • Include it in strings with interpolation (${…}).
  • The block above deploys three instances named dev_VM1, dev_VM2, dev_VM3.

for_each: multiple resources with distinct values

When some arguments need distinct values that can't be derived from an integer, count is a poor fit. for_each creates one instance per member of a set of strings or a map. This redundant code sets three specific zones:

resource "google_compute_instance" "VM1" {
name = "dev-us-central1-a"
location = "us-central1-a"
..
}
resource "google_compute_instance" "VM2" {
name = "dev-asia-east1-b"
location = "asia-east1-b"
..
}
resource "google_compute_instance" "VM3" {
name = "dev-europe-west4-a"
location = "europe-west4-a"
..
}

Collapse it with for_each, referencing each member through each.value:

resource "google_compute_instance" "dev_VM" {
for_each = toset( ["us-central1-a", "asia-east1-b", "europe-west4-a"] )
name = "dev-${each.value}"
 
zone = each.value
#other required arguments
}
NumbersWhat `for_each` creates
  • One instance per member of the set.
  • each.value is the current member, used here for both the name and the zone.
  • Result: three instances named dev-us-central1-a, dev-asia-east1-b, dev-europe-west4-a.
DECISION`count` or `for_each`?
Instances are almost identical, distinguished only by an indexcount
Instances need distinct values (zones, names, machine types) not derivable from an integerfor_each
Pick this when: count for interchangeable copies; for_each when each instance needs distinct, non-integer values

Resource dependencies

Exam guide§2.4

When you run the Terraform workflow, Terraform does not create resources in the order you wrote them. It first builds a dependency graph from your configuration and uses it to work out the correct order of operations - and to create independent resources in parallel when it is safe to do so.

The dependency graph

Terraform builds a dependency graph from your configuration to generate plans and refresh state. Attributes are interpolated at run time, and primitives - variables, output values, and providers - are connected in a dependency tree.

[root] root[root] provider["registry.terraform.io/hashicorp/google"] (close)google_compute_instance.vm_instancegoogle_compute_network.vpc_networkprovider["registry.terraform.io/hashicorp/google"]
Terraform's dependency graph for a VM in a VPC: root → provider (close) → the compute instance → the network → the provider. Terraform reads it bottom-up to order operations.

Two kinds of dependency

Terraform handles two kinds of dependency. It detects implicit ones on its own; explicit ones are invisible to it and you must declare them.

Implicit dependencyDependencies known toTerraform are detectedautomatically.Explicit dependencyDependencies unknownto Terraform must beconfigured explicitly.
Implicit dependencies are known to Terraform and detected automatically; explicit dependencies are unknown to Terraform and must be configured with depends_on.

Implicit dependencies

Sometimes one resource's creation depends on information generated by another, so Terraform can infer the ordering itself:

  • You cannot create a compute instance until its network exists.
  • You cannot assign a static IP to a Compute Engine instance until the static IP is reserved.

Terraform learns these relationships through interpolation expressions - references like google_compute_network.my_network.name. Use interpolation expressions whenever possible. Referencing my_network inside the instance's network argument creates an implicit (known) dependency on the google_compute_network block:

resource "google_compute_instance" "my_instance" {
//All mandatory arguments
 
network_interface {
//implicit dependency
network = google_compute_network.my_network.name
access_config {
}
}
}
 
resource "google_compute_network" "my_network" {
name = "my_network"
}

When Terraform reads this configuration it will:

  1. Ensure my_network is created before my_instance.
  2. Save the properties of my_network in state.
  3. Set the network argument on google_compute_instance to the value of the name argument from google_compute_network.

Run terraform apply and the ordering is visible in the output - the network is created first, then the instance:

Terraform createsthe network first.Once the network exists,the compute instanceis created.$terraform applygoogle_compute_network.my_network: Creating...google_compute_network.my_network: Still creating... [10s elapsed]google_compute_network.my_network: Still creating... [20s elapsed]google_compute_network.my_network: Still creating... [30s elapsed]google_compute_network.my_network: Creation complete after 32s[id=projects/qwiklabs-gcp-01-e973d950dd4a/global/networks/mynetwork]google_compute_instance.my_instance: Creating...google_compute_instance.my_instance: Still creating... [10s elapsed]google_compute_instance.my_instance: Creation complete after 13s[id=projects/qwiklabs-gcp-01-e973d950dd4a/zones/us-central1-a/instances/myinstance]
terraform apply output: the network is created and reaches completion first, then the compute instance is created - Terraform inferred the order from the implicit dependency.

Explicit dependencies

Some dependencies are not visible to Terraform - a resource must be created after another, but nothing in the configuration references it. For example, an application might read from a specific Cloud Storage bucket, but that dependency lives in the application code, so Terraform cannot see it. Declare these with the depends_on argument inside the dependent resource.

Clientdepends_onServerThe client VM can only be created when the server VM is created.
Explicit dependency: the client VM declares depends_on the server VM, so it can only be created after the server exists.

depends_on gives you control over processing order regardless of resource type, and can also be used within a module block. Its value is an expression pointing at the resource depended upon:

resource "resource_type" "resource_name" {
..
depends_on = [<resource_type>.<resource_name>]
}
 
resource "google_compute_instance" "client" {
...
depends_on = [google_compute_instance.server]
}
 
resource "google_compute_instance" "server" {
#All required configuration options
}

Running terraform apply, the server is created before the client because of the explicit dependency:

Server is createdbefore client.Due to the explicitdependency, the clientis created only afterthe server.$terraform applygoogle_compute_instance.server: Creating...google_compute_instance.server: Still creating... [10s elapsed]google_compute_instance.server: Creation complete after 12s[id=projects/qwiklabs-gcp-01-e973d950dd4a/zones/us-central1-a/instances/server]google_compute_instance.client: Creating...google_compute_instance.client: Still creating... [10s elapsed]google_compute_instance.client: Creation complete after 13s[id=projects/qwiklabs-gcp-01-e973d950dd4a/zones/us-central1-a/instances/client]
terraform apply output: the server VM is created before the client VM, because the client's depends_on forces the explicit ordering.
GotchaDefinition order in the file does not set creation order

The order in which resources are written in a configuration has no effect on how Terraform applies changes - Terraform derives ordering from the dependency graph, not from top-to-bottom position. Organize your .tf files however makes the most sense for you and your team; use depends_on when you need an ordering Terraform cannot infer.