Terraform: 40 Key Concepts in Simple English
import, removed and check.
What are the core building blocks?
These are the pieces you write in almost every configuration.
Basics
| # | Concept | What it means |
|---|---|---|
| 1 | Infrastructure as Code | Describe your infrastructure in code instead of clicking in a console. |
| 2 | Providers | Plugins that let Terraform talk to AWS, Azure, Kubernetes and others. |
| 3 | required_providers | Declare which providers and versions your code needs. |
| 4 | Resources | Things Terraform creates, updates and deletes. |
| 5 | Data sources | Read information about things that already exist, without managing them. |
| 6 | Variables | Pass values into your code from outside. |
| 7 | locals | Values calculated once inside your code and reused. |
| 8 | Outputs | Show useful results after apply, like an IP address. |
| 9 | sensitive | Hides a value from CLI output. See the warning below - it does not protect the secret. |
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}sensitive does not protect a secret. The docs are explicit: "Terraform stores the values of sensitive outputs in your state." Marking an output sensitive only redacts it from normal CLI display. Plain terraform output database_password does print <sensitive>, but the docs warn that if you use the command "with the -json or -raw flags", then "Terraform displays sensitive outputs in plain text". The real protection is securing your state, not the sensitive flag.Which files does Terraform actually read?
- 10.
*.tffiles - Terraform reads every.tffile in the folder. Names likemain.tf,variables.tfandoutputs.tfare conventions, not rules. - 11.
terraform.tfvarsand*.auto.tfvars- variable values that load automatically. - 12.
.terraform.lock.hcl- locks provider versions so the whole team uses the same ones. Commit it to git.
What does the everyday workflow look like?
Workflow commands
| # | Command | What it does |
|---|---|---|
| 13 | terraform init | Download providers and modules, prepare the folder. |
| 14 | terraform fmt | Format code in the standard style. |
| 15 | terraform validate | Check that the code is written correctly. |
| 16 | terraform plan | Preview what will change. |
| 17 | terraform apply | Make the planned changes. |
| 18 | terraform destroy | Remove everything this configuration manages. |
| 19 | -replace | Force one resource to be recreated, through a normal plan. |
When a resource is healthy as far as Terraform can tell but broken in reality, you can force a single resource to be recreated with the -replace option:
terraform apply -replace="aws_instance.example[0]"
The useful part is that this goes through a normal plan. You see exactly what will be destroyed and recreated before you approve it, and nothing touches state until you do.
How does Terraform remember what it manages?
- 20. State file - Terraform's memory of what it manages.
- 21. Backend - where the state is stored: local disk, S3, Azure Storage, HCP Terraform.
- 22. Remote state - state kept in a shared place so a team can work together.
- 23. State locking - stops two people changing the state at the same time.
- 24.
terraform statecommands -list,show,mv,rminspect or carefully edit the state. - 25. Workspaces (CLI) - separate state instances for the same configuration. They share one backend, so they are not an isolation boundary, and they are not the same as HCP Terraform workspaces.
How do you structure and reuse code?
- 26. Modules - reusable packages of Terraform code.
- 27. Provider alias - use the same provider for several regions or accounts.
- 28.
countvsfor_each- create several copies by number, or by map or set. - 29.
dynamicblocks - generate repeated nested blocks, such as firewall rules, from a list. - 30. Functions and expressions - helpers like
merge,lookup,cidrsubnet, and conditionals likea ? b : c.
for_each over count. With count, resources are tracked by position in a list. Remove an item from the middle and everything after it shifts down one index, so Terraform plans to recreate resources that did not actually change. With for_each, each instance is keyed by a stable map key or set value, so removing one leaves the others alone.How does Terraform decide what order to do things in?
- 31. Implicit dependencies - Terraform works out the order on its own when one resource references another. This is the preferred way.
- 32.
depends_on- set the order explicitly for hidden dependencies. - 33. Lifecycle rules -
create_before_destroy,prevent_destroy,ignore_changes. - 34.
movedandremovedblocks - see the warning below.
depends_on has a cost. It is not a free way to be explicit. The docs say: "You should only use depends_on as a last resort" because "it can cause Terraform to create more conservative plans that replace more resources than necessary." Terraform marks more values as unknown, and the docs note this is "especially likely when you use depends_on for modules". Reference the other resource directly wherever you can, and let Terraform infer the edge.
removed block destroys by default. This one is worth reading twice, because the common summary of it is wrong. People describe removed as "stop managing a resource without destroying it". That is only half true. The docs state: "By default, Terraform removes the resource from state and destroys the actual resource." To get the behaviour most people expect, you must opt in: "Set destroy to false to remove the resource from state without destroying the actual resource." Writing a bare removed block and assuming your infrastructure is safe is how you delete production.# Removes from state AND destroys the real resource
removed {
from = aws_instance.example
}
# Removes from state only, leaves the real resource alone
removed {
from = aws_instance.example
lifecycle {
destroy = false
}
}How do you keep it safe and tested?
- 35. Validation, preconditions and postconditions - rules that stop the run when values are wrong.
- 36.
checkblocks - see below. These behave differently from the rules above. - 37.
terraform test- automated tests for your modules. - 38. Ephemeral resources and write-only arguments - keep secrets out of plan and state files.
check blocks warn, they do not fail. Two things are commonly misstated here. First, they do not run only after apply. The docs say: "The check block executes as the last step of plan or apply operation." Second, and more importantly, a failed check does not stop anything: "Terraform reports a warning and continues executing the current operation."
That is the whole difference between check and preconditions or postconditions. A failed precondition halts the run. A failed check lets it finish and leaves a warning you might scroll past. Use preconditions for things that must be true, and checks for things you want to monitor. check blocks require Terraform v1.5.0 or later.
Ephemeral and write-only are two different features with different requirements. They are often mentioned in one breath, but they are not the same thing and they do not arrive together.
Keeping secrets out of state
| Feature | What the docs guarantee | Requirement |
|---|---|---|
| Ephemeral resources | "Terraform does not store information about ephemeral resources in state or plan files." | See note below |
| Write-only arguments | Terraform "does not store them in state or plan files". | "Terraform v.1.11 or later and use a resource that supports write-only arguments" |
Write-only arguments also depend on the provider, not just on your Terraform version. The docs put it this way: "The implementation of write-only arguments and their version arguments is provider-specific, so consult the Registry for more details about your specific provider." So a recent Terraform is necessary but not sufficient - the resource has to support it too.
How do you bring existing infrastructure under Terraform?
- 39. Import - bring existing resources under Terraform's control.
- 40. Drift detection -
terraform planshows manual changes made outside Terraform.
Prefer the import block over the terraform import command. The docs recommend the block directly: "Instead of manually importing resources, you can add the import block to your Terraform configurations." The reason they give is automation: "Using Terraform configurations lets you automate resource imports as part of your CI/CD pipelines."
The block lives in your configuration, so it is reviewable and repeatable. The command is a one-off that mutates state from someone's laptop.
import {
to = aws_instance.example
id = "i-abcd1234"
}
resource "aws_instance" "example" {
# ... configuration ...
}For drift, terraform plan -refresh-only shows only how reality differs from your state, and terraform apply -refresh-only accepts those differences into state without changing any infrastructure.
When should you use each?
Many of these come down to picking between two options that look interchangeable but are not.
Decision guide
| Situation | Use this | Not this |
|---|---|---|
| Several similar resources | for_each, keyed and stable | count, which shifts on removal |
| Ordering between resources | A direct reference, so Terraform infers it | depends_on, unless nothing else works |
| Recreating one resource | apply -replace=ADDRESS | Deleting and re-adding the resource block |
| Adopting existing infrastructure | import block in configuration | terraform import command |
| A rule that must hold | precondition or postcondition, which stops the run | check, which only warns |
| Something you want to monitor | check block | precondition, which blocks the apply |
| Separating prod from dev | Separate configurations and backends | CLI workspaces, which share a backend |
| Dropping a resource, keeping the infra | removed with destroy = false | A bare removed block, which destroys |
| Keeping a secret out of state | Write-only arguments or ephemeral resources | sensitive, which only hides output |
Bonus: provisioners can run scripts on machines, but the official docs are clear that they are a last resort. Reach for cloud-init, a configuration management tool, or a pre-baked image first.
Frequently asked questions
No. The docs state that Terraform stores the values of sensitive outputs in your state, and that running terraform output with the -json or -raw flags displays sensitive outputs in plain text. Plain terraform output does redact the value, which is what creates the false sense of safety. Protect the state file itself, and prefer write-only arguments or ephemeral resources for real secrets.
By default, yes. The docs say Terraform removes the resource from state and destroys the actual resource. To remove it from state while leaving the real resource in place, you must set destroy to false inside a lifecycle block. Do not assume a bare removed block is safe.
Run terraform apply with the -replace option and the resource address, for example terraform apply -replace="aws_instance.example[0]". This is the current way to do it. The replacement goes through a normal plan, so you see exactly what will be destroyed and recreated before you approve, and nothing is written to state until you accept. Use it when a resource looks healthy to Terraform but is broken in reality.
A failed precondition stops the run. A failed check does not - the docs say Terraform reports a warning and continues executing the current operation. Checks also run as the last step of a plan or apply, not only after apply. Use preconditions for invariants that must hold, and checks for conditions you want surfaced without blocking a deployment.
No. All CLI workspaces for a configuration share one backend, and the docs state that workspaces are not appropriate for system decomposition or deployments requiring separate credentials and access controls. Use separate configurations with separate backends instead. CLI workspaces are also a different concept from HCP Terraform workspaces.
Prefer for_each in most cases. count tracks instances by their position in a list, so removing an item from the middle shifts every later index and makes Terraform plan to recreate resources that did not change. for_each keys each instance by a map key or set value, which stays stable when other items are added or removed. count is still fine for a simple on or off toggle.
Official resources
- Output Values and the sensitive argument - HashiCorp
- terraform apply and the -replace option - HashiCorp
- The depends_on meta-argument - HashiCorp
- The removed block - HashiCorp
- Checks to validate infrastructure - HashiCorp
- Ephemeral resources - HashiCorp
- Write-only arguments - HashiCorp
- The import block - HashiCorp
- Manage multiple workspaces - HashiCorp