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.
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.
| Block Type | Purpose | Typical Example |
|---|---|---|
| resource | Create, update, and delete actual infrastructure | resource "aws_s3_bucket" "logs" { ... } |
| data | Read existing information (no creation) | data "aws_iam_policy_document" "example" { ... } |
| module | Invoke a reusable configuration | module "vpc" { source = "..." } |
| locals | Reuse expression results as local variables | locals { common_tags = { ... } } |
| output | Export values outside the module | output "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
}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.
Resource dependency DAG illustration
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 # the reference creates an implicit dependency
cidr_block = "10.0.1.0/24"
availability_zone = var.az
}
resource "null_resource" "post_config" {
# Only when an external requirement (file generation, say) demands the ordering
depends_on = [aws_subnet.app]
triggers = {
stamp = timestamp()
}
}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.
Examples of count, for_each, and provider aliases
provider "aws" {
region = var.region
}
provider "aws" {
alias = "dr"
region = var.dr_region
}
# count: simply N of them
resource "aws_iam_user" "ops" {
count = var.ops_user_count
name = "ops-${count.index}"
}
# for_each: managed individually by name
resource "aws_s3_bucket" "team" {
for_each = toset(var.team_names)
bucket = "nicheelab-${each.key}-bucket"
provider = aws # can be omitted
}
# Create it explicitly on the DR side
resource "aws_s3_bucket" "team_dr" {
for_each = toset(var.team_names)
bucket = "nicheelab-${each.key}-bucket-dr"
provider = aws.dr
}
# Reference example
output "team_buckets" {
value = { for k, v in aws_s3_bucket.team : k => v.id }
}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.
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 # the attribute reference creates the dependency
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
}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.
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] # a stopgap for tags that are edited by hand
}
}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.
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
}Associate
Question 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?
Correct answer: 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.
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.
Practice with certification-focused question sets
Try free questionsNicheeLab Editorial Team
NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.
HCL Syntax: Terraform's Configuration Language (2026)
HCL2 fundamentals for Terraform — blocks, attributes, expres...
Terraform Authoring & Operations Pro: Complete Guide (2026)
Tactics for the Terraform Pro exam — module authoring, works...
Terraform Providers: Plugin Management Fundamentals (2026)
Provider mechanics — required_providers, versions, mirrors, ...
Terraform Resource Blocks: Declarative Infra Units (2026)
Resource block fundamentals — addresses, references, common ...
Terraform Data Sources: Read-Only External Data (2026)
Data source basics — declaration, refresh behavior, dependen...