Terraform

Terraform Resource Block Deep Dive: The Smallest Unit of Resource Definition

2026-04-19
NicheeLab Editorial Team

The Resource block is the smallest unit in Terraform. It declares actual cloud entities (e.g., VPCs, instances, buckets) and is the fundamental building block that plan and apply operate on.

This article is grounded in the behaviors documented in the official docs and lays out the angles commonly tested on the exam, alongside the pitfalls that trip people up in the field.

Basic Structure and Naming of the Resource Block

A Resource block is defined by combining a resource type provided by a provider with a unique local name. Within a single module, you cannot have duplicates of the same type and name. You declare the desired state by supplying attributes (arguments).

Types are defined by the provider and conventionally carry a provider prefix (e.g., aws_, azurerm_, google_). The local name accepts alphanumerics and underscores and is used in subsequent references.

  • Minimum form: resource "<TYPE>" "<NAME>" { ... }
  • NAME must be unique within the same module
  • Attributes use HCL expressions such as map, list, and object
  • References take the form type.name.attribute
Block TypePurposeTypical Example
resourceCreate, update, and delete actual infrastructureresource "aws_s3_bucket" "logs" { ... }
dataRead existing information (no creation)data "aws_iam_policy_document" "example" { ... }
moduleInvoke a reusable configurationmodule "vpc" { source = "..." }
localsReuse expression results as local variableslocals { common_tags = { ... } }
outputExport values outside the moduleoutput "bucket_name" { value = aws_s3_bucket.logs.id }

A minimal Resource block example

resource "aws_s3_bucket" "logs" {
  bucket = "nicheelab-logs-${var.env}"
  tags = {
    Project = "NicheeLab"
    Env     = var.env
  }
}

output "bucket_id" {
  value = aws_s3_bucket.logs.id
}

Dependencies and the DAG: Implicit vs. Explicit

Terraform automatically builds a DAG (directed acyclic graph) from reference relationships, then uses it for parallel execution and ordering. Using attributes from another resource creates an implicit dependency.

Use depends_on only when you cannot reference but still need ordering. Excessive depends_on reduces parallelism, so first consider whether a reference is enough.

  • Implicit dependency: ordering is determined automatically by referencing another resource's attributes
  • Explicit dependency: keep depends_on = [resource.type.name] to the absolute minimum
  • Circular references are an error, so decompose them at design time

Resource dependency DAG illustration

aws_vpc.main(created first)aws_subnet.app(depends on VPC)aws_instance.web(depends on Subnet)aws_vpc.main → aws_subnet.app → aws_instance.web

Implicit and explicit dependency examples

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "app" {
  vpc_id            = aws_vpc.main.id  # 参照により暗黙依存
  cidr_block        = "10.0.1.0/24"
  availability_zone = var.az
}

resource "null_resource" "post_config" {
  # ファイル生成など外部要件で順序を保証したいときのみ
  depends_on = [aws_subnet.app]

  triggers = {
    stamp = timestamp()
  }
}

Meta-Arguments: count / for_each / provider / lifecycle

Use count with an integer for simple repetition, and for_each with a keyed collection when individual identification matters. provider lets you select specific credentials or regions via an alias.

lifecycle controls creation and replacement behavior, but overusing it because it is convenient makes diffs hard to resolve. Set it only when you have a clear reason.

  • count.index is zero-based; reference as resource.type.name[count.index]
  • for_each references each element as resource.type.name[each.key]
  • You can pick an alias like provider = aws.us_east
  • lifecycle includes create_before_destroy, prevent_destroy, ignore_changes, and more

Examples of count, for_each, and provider aliases

provider "aws" {
  region = var.region
}

provider "aws" {
  alias  = "dr"
  region = var.dr_region
}

# count: 単純なN個
resource "aws_iam_user" "ops" {
  count = var.ops_user_count
  name  = "ops-${count.index}"
}

# for_each: 名前で個別管理
resource "aws_s3_bucket" "team" {
  for_each = toset(var.team_names)
  bucket   = "nicheelab-${each.key}-bucket"
  provider = aws  # 明示省略可
}

# DR側へ明示的に作成
resource "aws_s3_bucket" "team_dr" {
  for_each = toset(var.team_names)
  bucket   = "nicheelab-${each.key}-bucket-dr"
  provider = aws.dr
}

# 参照例
output "team_buckets" {
  value = { for k, v in aws_s3_bucket.team : k => v.id }
}

Arguments vs. Attributes and the Basics of Referencing

Arguments are settable inputs, while attributes include outputs and read-only values that Terraform learns after creation. Attributes shown as computed in plan are finalized only after apply.

Reference another resource's attribute as type.name.attr, and access composite attributes via dot notation or indexing. Design carefully to avoid circular references by not casually folding output attributes into input values.

  • Whether a field is settable is determined by the provider resource's schema
  • Computed attributes are finalized after apply and may appear as unknown in plan
  • Wrapping values in outputs makes cross-module references explicit

Examples of attribute references and outputs

resource "aws_s3_bucket" "logs" {
  bucket = "nicheelab-logs-${var.env}"
  tags = var.common_tags
}

resource "aws_s3_bucket_policy" "logs" {
  bucket = aws_s3_bucket.logs.id  # 属性参照により依存
  policy = data.aws_iam_policy_document.allow_put.json
}

data "aws_iam_policy_document" "allow_put" {
  statement {
    actions   = ["s3:PutObject"]
    resources = ["${aws_s3_bucket.logs.arn}/*"]
    principals { type = "AWS" identifiers = [var.app_role_arn] }
  }
}

output "logs_bucket_arn" {
  value = aws_s3_bucket.logs.arn
}

Lifecycle Control: create_before_destroy / prevent_destroy / ignore_changes

Attribute changes that require replacement normally follow destroy → create. With create_before_destroy, the new resource is created first and the old one is deleted after cutover. Watch out for failures on resources with name collisions or other constraints.

prevent_destroy is effective against accidental deletion, but it also blocks planned destroys, so share the intent across the team and temporarily remove it when you need to apply changes. ignore_changes can suppress manual modifications, but over long-term operations it tends to become a source of drift.

  • create_before_destroy helps avoid downtime during cutover
  • Use prevent_destroy for tightly governed critical assets (e.g., shared VPCs)
  • Treat ignore_changes as temporary and codify the policy that it should not become permanent

Typical lifecycle patterns

resource "aws_lb" "public" {
  name               = "nicheelab-pub"
  internal           = false
  load_balancer_type = "application"

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_s3_bucket" "archive" {
  bucket = "nicheelab-archive-${var.env}"
  lifecycle {
    prevent_destroy = true
    ignore_changes  = [tags]  # タグを手動変更する運用の暫定対応
  }
}

Practical Tips and Associate Exam Focal Points

Prioritize future references and searchability when naming, so that type.name hints at the role. Consolidate tags and common settings in locals and reuse them across resources.

Common exam topics include understanding implicit dependencies, choosing between count and for_each, the meaning of each lifecycle setting, and the difference between data and resource. Aim for the level where you can read code and explain its behavior.

  • Centralize common tags in locals and apply them to each resource
  • Keep depends_on to the bare minimum and avoid over-using explicit dependencies
  • Design outputs as the contract at module boundaries
  • Practice explaining the reasons behind a terraform plan diff in your own words

A small example of locals and reuse

locals {
  common_tags = {
    Project = "NicheeLab"
    Owner   = var.owner
  }
}

resource "aws_s3_bucket" "app" {
  bucket = "nicheelab-app-${var.env}"
  tags   = local.common_tags
}

output "app_bucket" {
  value = aws_s3_bucket.app.id
}

Check with a Practice Question

Associate

問題 1

In a module, a subnet ID is passed as an argument to other resources, and the plan shows the dependent resources being created after the subnet. In this situation, what is the most appropriate choice regarding whether to add depends_on?

  1. A. No need to add it. An implicit dependency is already established through the attribute reference.
  2. B. You should always add depends_on just to be safe.
  3. C. Using for_each eliminates the need for depends_on.
  4. D. Setting lifecycle's create_before_destroy guarantees ordering.

正解: A

If you reference another resource's attributes, an implicit dependency is set automatically. You do not need to add depends_on for ordering. Use depends_on only when you cannot reference but still need to control order.

Frequently Asked Questions

Are there naming conventions for resource names?

HCL allows alphanumerics and underscores, and the name must be unique within the module. In practice, combining prefixes or suffixes that indicate role and environment (e.g., web, db, prod, dev) makes maintenance easier.

What is the difference between resource and data?

resource handles the creation, update, and deletion of actual infrastructure. data only reads existing information and does not create anything. Both build implicit dependencies through references in the same way.

When should I use depends_on?

Only when you cannot reference another resource's attributes directly but still need ordering guarantees. Examples include external provisioning (such as null_resource provisioners) or resources that must explicitly wait on ordering. Overusing it reduces parallelism.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

NicheeLab Editorial Team

NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.


Related articles
Terraform

HCL Syntax: Terraform's Configuration Language (2026)

HCL2 fundamentals for Terraform — blocks, attributes, expres...

Terraform

Terraform Authoring & Operations Pro: Complete Guide (2026)

Tactics for the Terraform Pro exam — module authoring, works...

Terraform

Terraform Providers: Plugin Management Fundamentals (2026)

Provider mechanics — required_providers, versions, mirrors, ...

Terraform

Terraform Resource Blocks: Declarative Infra Units (2026)

Resource block fundamentals — addresses, references, common ...

Terraform

Terraform Data Sources: Read-Only External Data (2026)

Data source basics — declaration, refresh behavior, dependen...

Browse all Terraform articles (102)
© 2026 NicheeLab All rights reserved.