Skip to content
DECLARE OR DRIFT

Terraform core

Terraform Fundamentals

Terraform becomes easier to learn when you treat it as two things: a graph of resources and a state file that records what Terraform manages.

The problem

Terraform manages infrastructure by building a graph of resources and applying changes in a safe order. HCL is the language you write. The graph is what Terraform executes.

That distinction matters. You are not writing a shell script. You describe objects and references. Terraform decides what must happen first.

  • HCL describes nodes and edges.
  • Providers translate nodes into API calls.
  • State connects your code to real remote objects.

Vocabulary

Terraform has a small vocabulary. A provider configures access to a platform. A resource manages an object. A data source reads an object Terraform does not own. A variable is an input. A local is a computed value inside the module. An output is a value exposed after apply.

Modules are directories of Terraform that behave like functions with inputs and outputs. Root modules are the directories you run Terraform from. Child modules are reusable building blocks called by a root.

  • Resource: Terraform owns lifecycle.
  • Data source: Terraform reads only.
  • Variable: caller supplies it.
  • Local: module computes it.
  • Output: module publishes it.

How the loop works

The normal command loop is init, fmt, validate, plan, apply. Init downloads providers and configures the backend. Fmt removes style noise. Validate catches structural mistakes. Plan compares desired config with state and remote reality. Apply performs the approved mutation.

Imports are how you bring existing objects under Terraform management. Import does not magically write perfect configuration. It links a remote object to a resource address in state, then you adjust code until the plan becomes no-op.

  • `terraform init` prepares plugins and backend.
  • `terraform plan` is your main learning tool.
  • `terraform import` maps an existing object to an address.
  • `terraform state` commands are powerful and should be treated like production database edits.

Common mistakes

The classic Terraform trap is making a module before you understand the root. Do not hide uncertainty behind abstractions. First write the resource directly, run a plan, inspect the shape, then extract a module when the pattern repeats.

Another mistake is treating state as a cache. It is not disposable. State contains object IDs, dependency relationships, and sometimes sensitive values. Losing or corrupting it can turn a routine change into a recovery job.

  • Do not start with clever module factories.
  • Do not share local state on a team.
  • Do not ignore replacement warnings because the diff looks small.

Working pattern

Use one root per deployable boundary: a DNS zone root, an app environment root, a shared identity root, or a database root. Keep state small enough that a plan can be understood by a human.

Use modules when you want to standardize an interface, not when you want to avoid typing. A good module hides a policy decision, such as naming, tags, logging, identity, or security defaults.

  • Start explicit, extract later.
  • Prefer boring file names: providers.tf, variables.tf, main.tf, outputs.tf.
  • Make plans mandatory in pull requests before production apply.

The Review

  • Can you explain what state stores and why it matters?
  • Can you tell when to use a resource versus a data source?
  • Can you read a module as an API with inputs and outputs?
  • Would a future import fit this structure without surgery?
opinionated repo structure readonly
infra/
  modules/
    app-service/
    dns-zone/
  live/
    prod/
      main.tf
      providers.tf
      backend.tf
      outputs.tf
    staging/
      main.tf