» Resources

For a hands-on tutorial, try the Get Started track on HashiCorp Learn.

Resources are the most important element in the Terraform language. Each resource block describes one or more infrastructure objects, such as virtual networks, compute instances, or higher-level components such as DNS records.

» Resource Syntax

Resource declarations can include a number of advanced features, but only a small subset are required for initial use. More advanced syntax features, such as single resource declarations that produce multiple similar remote objects, are described later in this page.

resource "aws_instance" "web" {
  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"

A resource block declares a resource of a given type ("aws_instance") with a given local name ("web"). The name is used to refer to this resource from elsewhere in the same Terraform module, but has no significance outside that module's scope.

The resource type and name together serve as an identifier for a given resource and so must be unique within a module.

Within the block body (between { and }) are the configuration arguments for the resource itself. Most arguments in this section depend on the resource type, and indeed in this example both ami and instance_type are arguments defined specifically for the aws_instance resource type.

» Resource Types

Each resource is associated with a single resource type, which determines the kind of infrastructure object it manages and what arguments and other attributes the resource supports.

» Providers

Each resource type is implemented by a provider, which is a plugin for Terraform that offers a collection of resource types. A provider usually provides resources to manage a single cloud or on-premises infrastructure platform. Providers are distributed separately from Terraform itself, but Terraform can automatically install most providers when initializing a working directory.

In order to manage resources, a Terraform module must specify which providers it requires. Additionally, most providers need some configuration in order to access their remote APIs, and the root module must provide that configuration.

For more information, see:

Terraform usually automatically determines which provider to use based on a resource type's name. (By convention, resource type names start with their provider's preferred local name.) When using multiple configurations of a provider (or non-preferred local provider names), you must use the provider meta-argument to manually choose an alternate provider configuration. See the section on provider below for more details.

» Resource Arguments

Most of the arguments within the body of a resource block are specific to the selected resource type. The resource type's documentation lists which arguments are available and how their values should be formatted.

The values for resource arguments can make full use of expressions and other dynamic Terraform language features.

There are also some meta-arguments that are defined by Terraform itself and apply across all resource types. (See Meta-Arguments below.)

» Documentation for Resource Types

Every Terraform provider has its own documentation, describing its resource types and their arguments.

Most publicly available providers are distributed on the Terraform Registry, which also hosts their documentation. When viewing a provider's page on the Terraform Registry, you can click the "Documentation" link in the header to browse its documentation. Provider documentation on the registry is versioned, and you can use the dropdown version menu in the header to switch which version's documentation you are viewing.

To browse the publicly available providers and their documentation, see the providers section of the Terraform Registry.

» Resource Behavior

A resource block declares that you want a particular infrastructure object to exist with the given settings. If you are writing a new configuration for the first time, the resources it defines will exist only in the configuration, and will not yet represent real infrastructure objects in the target platform.

Applying a Terraform configuration is the process of creating, updating, and destroying real infrastructure objects in order to make their settings match the configuration.

When Terraform creates a new infrastructure object represented by a resource block, the identifier for that real object is saved in Terraform's state, allowing it to be updated and destroyed in response to future changes. For resource blocks that already have an associated infrastructure object in the state, Terraform compares the actual configuration of the object with the arguments given in the configuration and, if necessary, updates the object to match the configuration.

This general behavior applies for all resources, regardless of type. The details of what it means to create, update, or destroy a resource are different for each resource type, but this standard set of verbs is common across them all.

The meta-arguments within resource blocks, documented in the sections below, allow some details of this standard resource behavior to be customized on a per-resource basis.

» Accessing Resource Attributes

Expressions within a Terraform module can access information about resources in the same module, and you can use that information to help configure other resources. Use the <RESOURCE TYPE>.<NAME>.<ATTRIBUTE> syntax to reference a resource attribute in an expression.

In addition to arguments specified in the configuration, resources often provide read-only attributes with information obtained from the remote API; this often includes things that can't be known until the resource is created, like the resource's unique random ID.

Many providers also include data sources, which are a special type of resource used only for looking up information.

For a list of the attributes a resource or data source type provides, consult its documentation; these are generally included in a second list below its list of configurable arguments.

For more information about referencing resource attributes in expressions, see Expressions: References to Resource Attributes.

» Resource Dependencies

Most resources in a configuration don't have any particular relationship, and Terraform can make changes to several unrelated resources in parallel.

However, some resources must be processed after other specific resources; sometimes this is because of how the resource works, and sometimes the resource's configuration just requires information generated by another resource.

Most resource dependencies are handled automatically. Terraform analyses any expressions within a resource block to find references to other objects, and treats those references as implicit ordering requirements when creating, updating, or destroying resources. Since most resources with behavioral dependencies on other resources also refer to those resources' data, it's usually not necessary to manually specify dependencies between resources.

However, some dependencies cannot be recognized implicitly in configuration. For example, if Terraform must manage access control policies and take actions that require those policies to be present, there is a hidden dependency between the access policy and a resource whose creation depends on it. In these rare cases, the depends_on meta-argument can explicitly specify a dependency.

» Meta-Arguments

Terraform CLI defines the following meta-arguments, which can be used with any resource type to change the behavior of resources:

These arguments often have additional restrictions on what language features can be used with them, which are described in each

» depends_on: Explicit Resource Dependencies

Use the depends_on meta-argument to handle hidden resource dependencies that Terraform can't automatically infer.

Explicitly specifying a dependency is only necessary when a resource relies on some other resource's behavior but doesn't access any of that resource's data in its arguments.

This argument is available in all resource blocks, regardless of resource type. For example:

resource "aws_iam_role" "example" {
  name = "example"

  # assume_role_policy is omitted for brevity in this example. See the
  # documentation for aws_iam_role for a complete example.
  assume_role_policy = "..."

resource "aws_iam_instance_profile" "example" {
  # Because this expression refers to the role, Terraform can infer
  # automatically that the role must be created first.
  role = aws_iam_role.example.name

resource "aws_iam_role_policy" "example" {
  name   = "example"
  role   = aws_iam_role.example.name
  policy = jsonencode({
    "Statement" = [{
      # This policy allows software running on the EC2 instance to
      # access the S3 API.
      "Action" = "s3:*",
      "Effect" = "Allow",

resource "aws_instance" "example" {
  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"

  # Terraform can infer from this that the instance profile must
  # be created before the EC2 instance.
  iam_instance_profile = aws_iam_instance_profile.example

  # However, if software running in this EC2 instance needs access
  # to the S3 API in order to boot properly, there is also a "hidden"
  # dependency on the aws_iam_role_policy that Terraform cannot
  # automatically infer, so it must be declared explicitly:
  depends_on = [

The depends_on meta-argument, if present, must be a list of references to other resources in the same module. Arbitrary expressions are not allowed in the depends_on argument value, because its value must be known before Terraform knows resource relationships and thus before it can safely evaluate expressions.

The depends_on argument should be used only as a last resort. When using it, always include a comment explaining why it is being used, to help future maintainers understand the purpose of the additional dependency.

» count: Multiple Resource Instances By Count

By default, a resource block configures one real infrastructure object. However, sometimes you want to manage several similar objects, such as a fixed pool of compute instances. Terraform has two ways to do this: count and for_each.

For a hands-on tutorial, try the Manage Similar Resources With Count guide on HashiCorp Learn.

The count meta-argument accepts a whole number, and creates that many instances of the resource. Each instance has a distinct infrastructure object associated with it (as described above in Resource Behavior), and each is separately created, updated, or destroyed when the configuration is applied.

resource "aws_instance" "server" {
  count = 4 # create four similar EC2 instances

  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"

  tags = {
    Name = "Server ${count.index}"

» The count Object

In resource blocks where count is set, an additional count object is available in expressions, so you can modify the configuration of each instance. This object has one attribute:

  • count.index — The distinct index number (starting with 0) corresponding to this instance.

» Referring to Instances

When count is set, Terraform distinguishes between the resource block itself and the multiple resource instances associated with it. Instances are identified by an index number, starting with 0.

  • <TYPE>.<NAME> (for example, aws_instance.server) refers to the resource block.
  • <TYPE>.<NAME>[<INDEX>] (for example, aws_instance.server[0], aws_instance.server[1], etc.) refers to individual instances.

This is different from resources without count or for_each, which can be referenced without an index or key.

» Using Expressions in count

The count meta-argument accepts numeric expressions. However, unlike most resource arguments, the count value must be known before Terraform performs any remote resource actions. This means count can't refer to any resource attributes that aren't known until after a configuration is applied (such as a unique ID generated by the remote API when an object is created).

» When to Use for_each Instead of count

If your resource instances are almost identical, count is appropriate. If some of their arguments need distinct values that can't be directly derived from an integer, it's safer to use for_each.

Before for_each was available, it was common to derive count from the length of a list and use count.index to look up the original list value:

variable "subnet_ids" {
  type = list(string)

resource "aws_instance" "server" {
  # Create one instance for each subnet
  count = length(var.subnet_ids)

  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"
  subnet_id     = var.subnet_ids[count.index]

  tags = {
    Name = "Server ${count.index}"

This was fragile, because the resource instances were still identified by their index instead of the string values in the list. If an element was removed from the middle of the list, every instance after that element would see its subnet_id value change, resulting in more remote object changes than intended. Using for_each gives the same flexibility without the extra churn.

» for_each: Multiple Resource Instances Defined By a Map, or Set of Strings

By default, a resource block configures one real infrastructure object. However, sometimes you want to manage several similar objects, such as a fixed pool of compute instances. Terraform has two ways to do this: count and for_each.

For a hands-on tutorial, try the Manage Similar Resources With For Each guide on HashiCorp Learn.

The for_each meta-argument accepts a map or a set of strings, and creates an instance for each item in that map or set. Each instance has a distinct infrastructure object associated with it (as described above in Resource Behavior), and each is separately created, updated, or destroyed when the configuration is applied.


resource "azurerm_resource_group" "rg" {
  for_each = {
    a_group = "eastus"
    another_group = "westus2"
  name     = each.key
  location = each.value

Set of strings:

resource "aws_iam_user" "the-accounts" {
  for_each = toset( ["Todd", "James", "Alice", "Dottie"] )
  name     = each.key

» The each Object

In resource blocks where for_each is set, an additional each object is available in expressions, so you can modify the configuration of each instance. This object has two attributes:

  • each.key — The map key (or set member) corresponding to this instance.
  • each.value — The map value corresponding to this instance. (If a set was provided, this is the same as each.key.)

» Using Expressions in for_each

The for_each meta-argument accepts map or set expressions. However, unlike most resource arguments, the for_each value must be known before Terraform performs any remote resource actions. This means for_each can't refer to any resource attributes that aren't known until after a configuration is applied (such as a unique ID generated by the remote API when an object is created).

The for_each value must be a map or set with one element per desired resource instance. If you need to declare resource instances based on a nested data structure or combinations of elements from multiple data structures you can use Terraform expressions and functions to derive a suitable value. For example:

» Referring to Instances

When for_each is set, Terraform distinguishes between the resource block itself and the multiple resource instances associated with it. Instances are identified by a map key (or set member) from the value provided to for_each.

  • <TYPE>.<NAME> (for example, azurerm_resource_group.rg) refers to the resource block.
  • <TYPE>.<NAME>[<KEY>] (for example, azurerm_resource_group.rg["a_group"], azurerm_resource_group.rg["another_group"], etc.) refers to individual instances.

This is different from resources without count or for_each, which can be referenced without an index or key.

» Using Sets

The Terraform language doesn't have a literal syntax for set values, but you can use the toset function to explicitly convert a list of strings to a set:

locals {
  subnet_ids = toset([

resource "aws_instance" "server" {
  for_each = local.subnet_ids

  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"
  subnet_id     = each.key # note: each.key and each.value are the same for a set

  tags = {
    Name = "Server ${each.key}"

Conversion from list to set discards the ordering of the items in the list and removes any duplicate elements. toset(["b", "a", "b"]) will produce a set containing only "a" and "b" in no particular order; the second "b" is discarded.

If you are writing a module with an input variable that will be used as a set of strings for for_each, you can set its type to set(string) to avoid the need for an explicit type conversion:

variable "subnet_ids" {
  type = set(string)

resource "aws_instance" "server" {
  for_each = var.subnet_ids

  # (and the other arguments as above)

» provider: Selecting a Non-default Provider Configuration

The provider meta-argument specifies which provider configuration to use, overriding Terraform's default behavior of selecting one based on the resource type name. Its value should be an unquoted <PROVIDER>.<ALIAS> reference.

As described in Provider Configuration, you can optionally create multiple configurations for a single provider (usually to manage resources in different regions of multi-region services). Each provider can have one default configuration, and any number of alternate configurations that include an extra name segment (or "alias").

By default, Terraform interprets the initial word in the resource type name (separated by underscores) as the local name of a provider, and uses that provider's default configuration. For example, the resource type google_compute_instance is associated automatically with the default configuration for the provider named google.

By using the provider meta-argument, you can select an alternate provider configuration for a resource:

# default configuration
provider "google" {
  region = "us-central1"

# alternate configuration, whose alias is "europe"
provider "google" {
  alias  = "europe"
  region = "europe-west1"

resource "google_compute_instance" "example" {
  # This "provider" meta-argument selects the google provider
  # configuration whose alias is "europe", rather than the
  # default configuration.
  provider = google.europe

  # ...

A resource always has an implicit dependency on its associated provider, to ensure that the provider is fully configured before any resource actions are taken.

The provider meta-argument expects a <PROVIDER>.<ALIAS> reference, which does not need to be quoted. Arbitrary expressions are not permitted for provider because it must be resolved while Terraform is constructing the dependency graph, before it is safe to evaluate expressions.

» lifecycle: Lifecycle Customizations

The general lifecycle for resources is described above in the Resource Behavior section. Some details of that behavior can be customized using the special nested lifecycle block within a resource block body:

resource "azurerm_resource_group" "example" {
  # ...

  lifecycle {
    create_before_destroy = true

The lifecycle block and its contents are meta-arguments, available for all resource blocks regardless of type. The following lifecycle meta-arguments are supported:

  • create_before_destroy (bool) - By default, when Terraform must make a change to a resource argument that cannot be updated in-place due to remote API limitations, Terraform will instead destroy the existing object and then create a new replacement object with the new configured arguments.

    The create_before_destroy meta-argument changes this behavior so that the new replacement object is created first, and then the prior object is destroyed only once the replacement is created.

    This is an opt-in behavior because many remote object types have unique name requirements or other constraints that must be accommodated for both a new and an old object to exist concurrently. Some resource types offer special options to append a random suffix onto each object name to avoid collisions, for example. Terraform CLI cannot automatically activate such features, so you must understand the constraints for each resource type before using create_before_destroy with it.

  • prevent_destroy (bool) - This meta-argument, when set to true, will cause Terraform to reject with an error any plan that would destroy the infrastructure object associated with the resource, as long as the argument remains present in the configuration.

    This can be used as a measure of safety against the accidental replacement of objects that may be costly to reproduce, such as database instances. However, it will make certain configuration changes impossible to apply, and will prevent the use of the terraform destroy command once such objects are created, and so this option should be used sparingly.

    Since this argument must be present in configuration for the protection to apply, note that this setting does not prevent the remote object from being destroyed if the resource block were removed from configuration entirely: in that case, the prevent_destroy setting is removed along with it, and so Terraform will allow the destroy operation to succeed.

  • ignore_changes (list of attribute names) - By default, Terraform detects any difference in the current settings of a real infrastructure object and plans to update the remote object to match configuration.

    In some rare cases, settings of a remote object are modified by processes outside of Terraform, which Terraform would then attempt to "fix" on the next run. In order to make Terraform share management responsibilities of a single object with a separate process, the ignore_changes meta-argument specifies resource attributes that Terraform should ignore when planning updates to the associated remote object.

    The arguments corresponding to the given attribute names are considered when planning a create operation, but are ignored when planning an update.

    resource "aws_instance" "example" {
      # ...
      lifecycle {
        ignore_changes = [
          # Ignore changes to tags, e.g. because a management agent
          # updates these based on some ruleset managed elsewhere.

    You can also ignore specific map elements by writing references like tags["Name"] in the ignore_changes list, though with an important caveat: the ignoring applies only to in-place updates to an existing key. Adding or removing a key is treated by Terraform as a change to the containing map itself rather than to the individual key, and so if you wish to ignore changes to a particular tag made by an external system you must ensure that the Terraform configuration creates a placeholder element for that tag name so that the external system changes will be understood as an in-place edit of that key:

    resource "aws_instance" "example" {
      # ...
      tags = {
        # Initial value for Name is overridden by our automatic scheduled
        # re-tagging process; changes to this are ignored by ignore_changes
        # below.
        Name = "placeholder"
      lifecycle {
        ignore_changes = [

    Instead of a list, the special keyword all may be used to instruct Terraform to ignore all attributes, which means that Terraform can create and destroy the remote object but will never propose updates to it.

    Only attributes defined by the resource type can be ignored. ignore_changes cannot be applied to itself or to any other meta-arguments.

The lifecycle settings all effect how Terraform constructs and traverses the dependency graph. As a result, only literal values can be used because the processing happens too early for arbitrary expression evaluation.

» provisioner and connection: Resource Provisioners

For hands-on tutorials about handling provisioning actions declaratively, try the Provision Infrastructure Deployed with Terraform track on HashiCorp Learn.

Some infrastructure objects require some special actions to be taken after they are created before they can become fully functional. For example, compute instances may require configuration to be uploaded or a configuration management program to be run before they can begin their intended operation.

Create-time actions like these can be described using resource provisioners. A provisioner is another type of plugin supported by Terraform, and each provisioner takes a different kind of action in the context of a resource being created.

Provisioning steps should be used sparingly, since they represent non-declarative actions taken during the creation of a resource and so Terraform is not able to model changes to them as it can for the declarative portions of the Terraform language.

Provisioners can also be defined to run when a resource is destroyed, with certain limitations.

The provisioner and connection block types within resource blocks are meta-arguments available across all resource types. Provisioners and their usage are described in more detail in the Provisioners section.

» Local-only Resources

While most resource types correspond to an infrastructure object type that is managed via a remote network API, there are certain specialized resource types that operate only within Terraform itself, calculating some results and saving those results in the state for future use.

For example, local-only resource types exist for generating private keys, issuing self-signed TLS certificates, and even generating random ids. While these resource types often have a more marginal purpose than those managing "real" infrastructure objects, they can be useful as glue to help connect together other resources.

The behavior of local-only resources is the same as all other resources, but their result data exists only within the Terraform state. "Destroying" such a resource means only to remove it from the state, discarding its data.

» Operation Timeouts

Some resource types provide a special timeouts nested block argument that allows you to customize how long certain operations are allowed to take before being considered to have failed. For example, aws_db_instance allows configurable timeouts for create, update and delete operations.

Timeouts are handled entirely by the resource type implementation in the provider, but resource types offering these features follow the convention of defining a child block called timeouts that has a nested argument named after each operation that has a configurable timeout value. Each of these arguments takes a string representation of a duration, such as "60m" for 60 minutes, "10s" for ten seconds, or "2h" for two hours.

resource "aws_db_instance" "example" {
  # ...

  timeouts {
    create = "60m"
    delete = "2h"

The set of configurable operations is chosen by each resource type. Most resource types do not support the timeouts block at all. Consult the documentation for each resource type to see which operations it offers for configuration, if any.