Resources are the things Terraform creates. Reference one from another to build dependencies, control replacement with lifecycle, and reach for provisioners only as a last resort.
Why: a resource is one real object Terraform manages. The two labels are the type (random_pet, from the provider) and a local name (server) you use to refer to it elsewhere. Arguments in the body set its desired state. Apply this and Terraform creates a random two-word name you can reuse.
resource "random_pet" "server" {
length = 2
separator = "-"
}
resource "random_password" "db" {
length = 16
special = true
}Why: when one resource references another's attribute, Terraform automatically creates the dependency and orders the work correctly — no need to say so. Here the file's content reads the random_pet's id, so Terraform creates the pet first. The reference form is type.name.attribute.
resource "random_pet" "server" {
length = 2
}
resource "local_file" "name" {
filename = "server-name.txt"
content = random_pet.server.id # <- creates the dependency
}Why: occasionally a dependency exists that Terraform cannot see — a resource must exist before another for a reason not expressed by any attribute reference. depends_on forces the order. Reach for it only when an implicit reference will not do; over-using it slows and complicates the graph.
resource "local_file" "config" {
filename = "app.conf"
content = "ready"
}
resource "local_file" "marker" {
filename = "started.txt"
content = "ok"
depends_on = [local_file.config] # force config first
}Why: by default, changing an immutable attribute makes Terraform destroy and recreate a resource — sometimes with downtime. The lifecycle block tunes this: create_before_destroy makes the new one before removing the old, prevent_destroy blocks accidental deletion, and ignore_changes tells Terraform to stop fighting an attribute changed outside it.
resource "local_file" "important" {
filename = "keep.txt"
content = "do not lose me"
lifecycle {
create_before_destroy = true
prevent_destroy = true # apply/destroy will refuse to delete it
ignore_changes = [content] # don't revert manual edits
}
}Why: a provisioner runs a script as part of create or destroy — local-exec on your machine, remote-exec over SSH on the resource. HashiCorp explicitly calls them a last resort: they break the plan/apply model and are not tracked in state. Prefer a provider, a cloud-init script, or a configuration tool. Know they exist; avoid them when you can.
resource "random_pet" "server" {
length = 2
# Runs a command locally after the resource is created.
provisioner "local-exec" {
command = "echo Created random_pet.server.id >> provision.log"
}
}