Technology handbook
Terraform
Configuration language, providers, state, modules, plans and safe change management.
- Topics
- 24
- Sections
- 4
Language fundamentals
Providers
Declare and pin the providers a configuration requires.
- A provider is the plugin that translates configuration into API calls.
- Pin a version range so a provider release cannot change tomorrow's plan.
- Alias a provider when one configuration spans two regions or accounts.
Resources
Describe a piece of infrastructure and let Terraform reconcile it.
- A resource block names a type and a local name that together form its address.
- Arguments describe the wanted end state, not the steps to reach it.
- Some arguments force replacement when changed, which the plan marks explicitly.
Variables and validation
Parameterize a configuration and reject invalid input early.
- Typed variables reject a wrong shape before any API call is made.
- A validation block turns a business rule into a plan-time error message.
- Mark a variable sensitive so its value is redacted from plan output.
Outputs
Expose values other configurations or operators need.
- An output publishes a computed value such as an endpoint or an ARN.
- Root outputs are what a pipeline reads after an apply finishes.
- Marking an output sensitive keeps it out of logs but not out of state.
Locals and expressions
Name intermediate values instead of repeating an expression.
- A local is computed once and reused, unlike a variable supplied from outside.
- Expressions cover conditionals, for comprehensions and string templates.
- Naming a repeated expression once keeps a rename to a single edit.
Data sources
Read something Terraform does not manage without adopting it.
- A data source reads existing infrastructure without taking ownership of it.
- It refreshes on every plan, so a changed upstream value shows as a diff.
- Use one to look up an AMI, a zone or a network another team owns.
State
State purpose
Understand why Terraform records what it created and what breaks without it.
- State maps each configuration address to a real remote object id.
- Without it Terraform cannot tell a create apart from an update.
- It also caches attribute values so a plan need not read everything again.
Remote backends
Store state centrally so a team shares one source of truth.
- A remote backend puts state where every machine and pipeline reads the same copy.
- Backends add locking, versioning and encryption that a local file has not.
- Initialising a new backend migrates the existing state rather than discarding it.
State locking
Prevent two concurrent applies from corrupting shared state.
- A lock is taken for the duration of a plan or an apply and released after.
- Two unlocked applies can interleave writes and leave state describing neither.
- A stale lock from a crashed run is cleared deliberately, never automatically.
Workspaces
Keep separate state per environment from one configuration.
- Each workspace carries its own state under the same configuration directory.
- They suit environments that differ only in variable values, not in shape.
- Separate directories or backends scale better once environments truly diverge.
Import
Bring existing infrastructure under management without recreating it.
- An import block adopts a live object into state at a chosen address.
- The configuration must already describe the object or the next plan wants changes.
- Import writes state only; it never creates or modifies the real resource.
State surgery
Move or remove state entries deliberately, and know the risk.
- Moving an entry rewrites an address without touching live infrastructure.
- Removing an entry makes Terraform forget a resource that keeps on running.
- Take a state backup first, because these commands have no undo.
Modules & composition
Module structure
Package a reusable unit with clear inputs and outputs.
- A module is a directory of configuration with declared inputs and outputs.
- Keep provider configuration in the root and pass it down rather than nesting it.
- A module with dozens of inputs is usually two modules that have not been split.
Module sources and versions
Pin a module version so a plan does not change beneath you.
- A source can be a local path, a registry entry or a git reference.
- Pin registry modules to an exact version so plans stay reproducible.
- A git source without a tag or commit re-resolves and can shift under you.
Composition over inheritance
Build environments by composing modules rather than branching inside one.
- Environments differ by the arguments passed in, not by conditionals inside.
- A root configuration per environment stays readable as the estate grows.
- Branching inside one module makes every environment share one blast radius.
count and for_each
Create many similar resources and understand how each key maps to state.
- count keys instances by index, so removing the middle one reshuffles the rest.
- for_each keys by a map or set member, so each instance has a stable identity.
- Prefer for_each whenever the collection can change in the middle.
Dynamic blocks
Generate repeated nested blocks from a collection.
- A dynamic block expands a collection into repeated nested blocks.
- Use it for rule lists such as security group ingress, not for whole resources.
- Overusing it hides the shape of the configuration from anyone reading a diff.
Dependency ordering
Rely on implicit references and reach for depends_on only when required.
- Referencing another resource's attribute creates the dependency automatically.
- depends_on is for ordering that no attribute reference expresses, such as IAM propagation.
- An unnecessary depends_on serialises work that could otherwise run in parallel.
Operating safely
Plan review
Read a plan as a change proposal and reject surprises before applying.
- Read the counts first: create, update, destroy and replace.
- Any replacement of a stateful resource deserves a reason before it is approved.
- Save the plan to a file and apply that file, so no drift slips in between.
Targeted applies
Limit a change to specific addresses, and know why that is a last resort.
- Targeting applies one address and skips the rest of the graph.
- It leaves state partly reconciled, so the next full plan may still show work.
- Reserve it for recovering from a failed apply, not for routine change.
Moved and removed blocks
Refactor addresses without destroying live infrastructure.
- A moved block tells Terraform an address was renamed, not replaced.
- It lives in configuration, so the refactor is reviewed like any other change.
- A removed block drops management of a resource while leaving it running.
Drift detection
Find changes made outside Terraform and decide whether to adopt or revert.
- A plan with no configuration change still reports what someone altered by hand.
- Scheduled plans surface drift before it collides with a real deployment.
- Decide per case whether to revert the change or write it into configuration.
Secrets handling
Keep credentials out of configuration and out of state where possible.
- Read credentials from the environment or a secret store, never from a committed file.
- Values a provider returns land in state in plain text regardless of sensitivity.
- Encrypt the state backend and restrict who can read it as if it were the secret.
CI pipelines
Plan on a pull request and apply only after review on the default branch.
- A pull request runs plan and posts it as the change proposal for review.
- Apply runs only from the default branch, with credentials no fork can reach.
- Apply the exact saved plan so what was reviewed is what runs.
Sources
- Terraform official documentation