Resources
What are resources?
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.
- 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:
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:
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:
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
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.
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:
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:
Meta-arguments
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-argument | What it does |
|---|---|
count | Create multiple instances according to the value assigned to the count. |
for_each | Create multiple resource instances as per a set of strings (or a map). |
depends_on | Specify an explicit dependency. |
lifecycle | Define the life cycle of a resource. |
provider | Select a non-default provider configuration. |
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.
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.
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:
Add the count argument at the top of the block instead. count tells Terraform
to create that many instances of the same kind:
count.indexis 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:
Collapse it with for_each, referencing each member through each.value:
- One instance per member of the set.
each.valueis 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.
countfor_eachResource dependencies
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.
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 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:
When Terraform reads this configuration it will:
- Ensure
my_networkis created beforemy_instance. - Save the properties of
my_networkin state. - Set the
networkargument ongoogle_compute_instanceto the value of thenameargument fromgoogle_compute_network.
Run terraform apply and the ordering is visible in the output - the network is
created first, then the instance:
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.
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:
Running terraform apply, the server is created before the client because of
the explicit dependency:
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.