linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Knowledge base
  • Cheat sheet
  • Capstone
  • Interview prep
home/terraform/kb/Resources & data sources/tf-resource-block

kb/resources ── Resources & data sources ── beginner

Resource block: the main building block of Terraform

A resource block tells Terraform "create this thing in the cloud." It has three parts: the resource type (what it is), the name (how you refer to it internally), and the arguments (how to configure it). Writing these blocks is what you spend 90% of your time doing in Terraform.

view as markdown

Anatomy of a resource block

hcl
resource "aws_s3_bucket" "demo" {
  bucket = "my-bucket-12345"
  tags = {
    Owner = "student"
  }
}

There are four things to notice here:

  1. The resource keyword tells Terraform "this is an entity to create."
  2. The resource type "aws_s3_bucket" is what you are creating. The type comes from the provider. The AWS provider has 800+ types: aws_s3_bucket, aws_instance, aws_iam_role, and so on.
  3. The resource name "demo" is your internal label. You use it to reference this resource from other parts of HCL: aws_s3_bucket.demo.id.
  4. The block body holds the arguments. Some are required (bucket), some are optional (tags).

The name exists only inside Terraform. In AWS the bucket is named my-bucket-12345 (the value of the bucket argument). demo is just like a variable in code.

Arguments vs attributes

These two words are easy to mix up.

  • Arguments are what you write in HCL. You supply them: bucket = "...", region = "...".
  • Attributes are what the cloud returns after creation. You read them: aws_s3_bucket.demo.id, aws_s3_bucket.demo.arn.

Some attributes are known immediately (the values you wrote as arguments). Others are available only after apply (for example, id, arn, creation_date). Before apply, the plan shows them as (known after apply).

Resource address

Every resource has a unique address within the project: type.name.

hcl
aws_s3_bucket.demo           # the resource itself
aws_s3_bucket.demo.bucket    # its "bucket" attribute
aws_s3_bucket.demo.arn       # its "arn" attribute

The address is stable across runs. If you rename "demo" to "main", Terraform treats that as "delete the old one, create a new one," even if nothing changed in the cloud. To avoid recreation on rename, use the moved {} block (advanced).

References between resources

The most useful pattern is linking resources together:

hcl
resource "aws_s3_bucket" "demo" {
  bucket = "my-bucket-12345"
}
resource "aws_s3_bucket_versioning" "demo" {
  bucket = aws_s3_bucket.demo.id   # ← reference to the bucket above
  versioning_configuration {
    status = "Enabled"
  }
}

When Terraform sees aws_s3_bucket.demo.id, it understands: "create that bucket first, then this resource." This is the automatic dependency graph. See tf-depends-on.

Multiple instances of the same resource

If you need three similar buckets, there are two ways:

hcl
# Method 1: count, indexed by number
resource "aws_s3_bucket" "many" {
  count  = 3
  bucket = "bucket-${count.index}"
}
# Method 2: for_each, indexed by key
resource "aws_s3_bucket" "regional" {
  for_each = toset(["us", "eu", "ap"])
  bucket   = "bucket-${each.key}"
}

The address of each instance:

  • aws_s3_bucket.many[0], aws_s3_bucket.many[1], aws_s3_bucket.many[2]
  • aws_s3_bucket.regional["us"], aws_s3_bucket.regional["eu"], ...

See tf-count-for-each for details on when to use which.

Pitfalls

  • The resource name is not the name in the cloud. If you rename the HCL label from "demo" to "main", Terraform will drop and recreate the resource, even if the content is identical. Use the moved {} block if you need to rename safely.

  • Arguments are strictly typed. If bucket expects a string and you write a number, terraform validate will fail. This is a feature: errors are caught before anything touches the cloud.

  • (known after apply) is normal. Do not be alarmed when you see it in the plan. It means "this value will exist after creation." For example, id: the bucket name itself Terraform knows right away, but arn is available only after the API responds.

  • Not all arguments support in-place updates. Some resource attributes cannot change without recreation (for example, the EC2 instance type). Terraform marks these in the plan as -/+ resource ..., meaning "destroy and recreate." Be careful: data will be lost.

  • The lifecycle block guards against accidents. See tf-resource-lifecycle: prevent_destroy = true blocks deletion, and ignore_changes ignores drift on specific attributes.

§ команды

bash
terraform state list

List all resources currently in state with their addresses.

bash
terraform state show aws_s3_bucket.demo

Show all attributes of a specific resource, including those you did not set yourself.

bash
terraform plan -target=aws_s3_bucket.demo

Show the plan for a single resource only. Useful when the plan for a large project is slow.

§ см. также

  • hcl-syntaxHCL: the language you write Terraform inHCL (HashiCorp Configuration Language) is the language you use to describe the desired state of your infrastructure. It looks like JSON, but it is easier to read: you can write comments, variables, and loops.
  • tf-data-sourcedata block: reading what already exists in the clouddata is a block that queries existing infrastructure and returns its attributes to HCL. Terraform creates nothing; it only reads. Use it to reference resources that were not created by Terraform or that live in a different project.
  • tf-resource-lifecyclelifecycle: controlling resource behaviorThe lifecycle block configures four behaviors: create_before_destroy (zero-downtime replacement), prevent_destroy (deletion guard), ignore_changes (ignore drift on specific attributes), replace_triggered_by (force replacement on an external signal).
  • tf-depends-onResource dependencies: explicit and implicitTerraform automatically computes the creation order of resources from references in HCL (implicit dependencies). When no such reference exists but order matters, use depends_on. Use it rarely; it is usually a signal that the architecture needs reconsideration.
  • tf-referencesReferences in HCL: how to read aws_s3_bucket.demo.bucketAny value in HCL is reachable through an address: var.x, local.x, aws_s3_bucket.demo.arn, module.net.vpc_id, data.aws_region.current.name. Knowing this syntax is half of your productivity in Terraform.
  • tf-applyterraform apply: apply a plan to a real cloudapply takes the result of plan and actually calls the cloud API: it creates, changes, and deletes resources. After apply, the state is updated. This is the command that turns money into infrastructure.
Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies