Architecture and Core Concepts

Authors

K19G

grok-bot

Updated

October 31, 2026

Architecture and Core Concepts

Six words carry the rest of the book: configuration, plan, state, provider, resource, data source. This chapter defines them before any cloud account appears.

Learning goals

  • Separate what you write from what tofu does
  • Walk init, plan, apply, and destroy without memorising flags
  • Explain why state exists
  • Tell a resource from a data source

The Declarative Paradigm: What You Define vs. What Happens

You write the desired end state. You do not write the API calls. A resource block says “this object should exist with these arguments.” OpenTofu’s job is to notice the difference between that text and the recorded reality, and to call the provider until the difference is gone or the plan says it cannot be done without a destructive change.

That is declarative in the useful sense, not in the absolute sense. The order of blocks in a file is not the order of API calls. A local-exec provisioner is an imperative escape hatch, which is why chapter 10 discourages it. Arguments you set are desired state. Attributes the provider fills in (an id, a generated name) are observed state. Mixing those up is the first source of plans that fight you.

Idempotence is the promise: apply, then apply again, and the second plan is empty when nothing outside the configuration changed. Drift is the world editing itself. A person in a console, or another tool, changes an object the state still tracks. The next plan proposes to put it back. That proposal is the point of the tool.

OpenTofu’s Execution Model: Plan, Apply, Destroy

The commands you will actually live in:

Command Reads Writes Talks to the infrastructure
tofu init required_providers, backend, modules .terraform/, lock file Downloads providers and modules
tofu plan Configuration, state, data sources A plan file if you ask Refreshes and computes a diff
tofu apply A plan, or it makes one and waits State Creates, updates, and destroys objects
tofu destroy Configuration and state State Destroys what state tracks

plan is a dry run with teeth. It refreshes state by default, so it does contact APIs. It does not change infrastructure. apply with no plan file plans and then prompts. In automation you save the plan (tofu plan -out=tfplan) and apply that exact file (tofu apply tfplan) so the change a human approved is the change that runs.

Destroy is not the opposite of a git revert. It deletes the objects in state. A revert of the configuration, followed by apply, also deletes objects that disappeared from the configuration. Both are destructive. Chapter 9’s prevent_destroy is how you make a specific resource refuse.

Providers: The Bridge Between Configuration and Reality

OpenTofu does not know what an S3 bucket is. A provider plugin does. tofu starts the plugin, hands it the resource configuration, and stores whatever identity the plugin returns.

The required_providers block names the source and the version. Init downloads the plugin and writes .terraform.lock.hcl. That lock file is how the next machine gets the same plugin build. Commit it. Chapter 6 is authentication, aliases, and version constraints. For now, the bridge is: configuration in, API calls out, attributes back.

A provider configuration block (provider "aws" { ... }) is not a resource. It is credentials, region, and endpoints for every resource of that type. No block is required for providers that need no credentials. random is the one this book applies for real.

Resources: The Fundamental Building Blocks

A resource has a type and a local name: resource "random_pet" "service". The address you use everywhere else is random_pet.service. The type is the provider’s. The local name is yours, unique among that type in the module.

Arguments are what you set. After apply, the state holds those plus computed attributes. The next plan compares both. Changing an argument that the provider cannot update in place produces a destroy and a create. The plan says so with -/+ or a replacement. Read that line before you approve it.

count and for_each make many instances from one block. Instances have addresses like random_pet.service["api"]. Those addresses are how state finds the object. Renaming the key is a destroy and a create unless you move the state. Chapter 9 covers moved blocks and dependencies.

State: Why It Exists and How It Works

The configuration says what you want. It does not remember the id of the object it created last Tuesday. State does. State is a JSON document mapping each resource address to a provider-specific id and the attribute values from the last refresh.

Without state, the next apply would try to create a second copy, or would have to guess which remote object belongs to which block. Guessing is how tools delete the wrong thing. State is the memory that makes the guess unnecessary.

Local state is a file named terraform.tfstate in the working directory. It is enough for the pet example. It is the wrong place for anything a second person, or a pipeline, must update. Two applies against two copies of state create two worlds. Chapter 7 moves state to a backend with locking. State also stores attribute values, including ones marked sensitive. Sensitive means “do not print it in the plan UI.” It does not mean “omit it from the state file.”

Data Sources: Reading the World Without Creating It

A data source reads. It does not create, update, or destroy. data "aws_ami" "latest" asks the provider a question and stores the answer for the rest of the configuration to interpolate.

Data sources are refreshed during plan. A data source that changes under you changes the plan, which is what you want when you are looking up a current AMI, and what you do not want when the lookup is a moving target you should have pinned. Prefer a resource for anything you created and now need to remember. Prefer a data source for something that already existed and is owned elsewhere.

Data sources land in state too, as a cache of the last read. They are not a second source of truth. The next refresh overwrites them.

Summary

  • You declare the end state. OpenTofu computes the API calls
  • Init fetches plugins. Plan diffs. Apply changes. Destroy deletes what state tracks
  • Providers speak APIs. Resources are the objects. State remembers their ids
  • A data source reads. It does not own the object
  • A second apply is empty only when the world still matches the configuration and the state

A configuration that is only these ideas

terraform {
  required_version = "~> 1.13.0"

  required_providers {
    random = {
      source  = "hashicorp/random"
      version = "~> 3.6"
    }
  }
}

resource "random_pet" "service" {
  length = 2
}

output "name" {
  value = random_pet.service.id
}

tofu init, tofu plan, and tofu apply against that file are the whole execution model with no account attached.