Skip to content
shellhacks.dev
Automation

How to Use Terraform Basics for Infrastructure Automation

Learn Terraform basics for infrastructure automation: providers, resources, plan and apply, state, variables and outputs, with a small working example.

6 min read
Summarize in AI
Illustration for "How to Use Terraform Basics for Infrastructure Automation": a terminal session, a directory tree, environment variables.

Clicking through a cloud console cannot be reviewed, repeated or rolled back. Terraform replaces that with files you keep in version control and a tool that makes reality match them. It sits next to configuration tools like Ansible: Terraform creates the servers and networks, and Ansible or cloud-init configures what runs on them. The examples assume Terraform 1.9 and use the local and random providers so nothing costs money.

A first configuration

TOML
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "~> 2.5"
    }
  }
}

variable "greeting" {
  type    = string
  default = "hello from terraform"
}

resource "local_file" "motd" {
  filename = "${path.module}/motd.txt"
  content  = var.greeting
}

output "path" {
  value = local_file.motd.filename
}

The same structure applies when the resource is a virtual machine or a DNS record. A provider block pins the plugin that talks to an API. A resource block declares one object, a variable takes input and an output prints a result. The file name does not matter; all .tf files in a directory form one configuration.

Init, plan, apply

Shell
terraform init
terraform plan
terraform apply
Text
Terraform will perform the following actions:

  # local_file.motd will be created
  + resource "local_file" "motd" {
      + content  = "hello from terraform"
      + filename = "./motd.txt"
    }

Plan: 1 to add, 0 to change, 0 to destroy.

init downloads providers into .terraform/ and writes a lock file, .terraform.lock.hcl, which belongs in version control. plan compares the files, the state and the real world, and prints the diff. apply repeats the plan and asks for confirmation. For automation use terraform plan -out=tfplan and then terraform apply tfplan, so the exact reviewed plan is the one applied.

Change a variable

Shell
terraform apply -var 'greeting=hello again'
cat motd.txt

Terraform sees the content differs and replaces only that resource. This is the part that feels like idempotency: running apply twice with the same inputs changes nothing the second time. Variables can also come from terraform.tfvars or from TF_VAR_greeting in the environment.

State is the sensitive part

Terraform records what it created in terraform.tfstate. Lose it and Terraform no longer knows its resources exist; share it carelessly and it may expose passwords, since many providers store them in plain text there. Keep state out of Git, use a remote backend such as an object store with locking when more than one person applies, and restrict who can read it. The command terraform state list shows what is tracked, and terraform import brings an existing resource under management.

How Terraform thinks

Terraform is declarative: you describe the infrastructure you want, and the tool works out the steps to get there. The configuration lists resources, such as a network, a server or a DNS record, with their settings. On each run, Terraform reads the state it recorded earlier, asks the provider what exists now, compares both with the configuration and produces a plan of creates, changes and deletions. This is different from a script, which executes commands in order regardless of what already exists. The plan is the safety feature: it shows the effect of a change before it happens, and reading it carefully is the most important habit when using the tool.

Resources form a dependency graph, built from references between them. When one resource uses an attribute of another, Terraform creates them in the right order and in parallel where it can, and destroys them in reverse.

Variables, outputs and structure

Hard-coded values make a configuration useful once. Input variables parameterise it, with types, defaults and validation rules, and values come from files, the command line or the environment. Outputs expose results, such as an address, for other configurations and for people. Keep the files organised by purpose: a file for providers, one for variables, one for outputs and the resources grouped sensibly. Modules package a set of resources behind variables and outputs, which allows reuse and a consistent shape across environments. Start without modules until a pattern repeats, then extract one; premature abstraction makes small projects harder to read.

Format the code with terraform fmt and check it with terraform validate in CI. Pin the provider and Terraform versions in the configuration, so a new release does not change behaviour under you.

State needs care

The state file maps resources in the configuration to real objects, and without it Terraform would try to create everything again. It can contain secrets in plain text, such as database passwords, so it must be protected like a credential. Do not commit it to version control. For anything beyond a personal experiment, use a remote backend with encryption, access control and locking, so two people cannot apply at once and corrupt the file. Back it up, and enable versioning on the storage so a bad write can be reverted. Never edit it by hand; use the terraform state commands to move, remove or import resources.

Drift occurs when someone changes a resource outside Terraform. The next plan shows the difference. Decide in each case whether the configuration or the real object is right, then bring them back together.

Working safely in a team

Run plan in CI on every change and post the output to the review, so reviewers see the effect and not just the code. Apply from a controlled pipeline using the saved plan file, terraform apply plan.tfplan, which guarantees that what was reviewed is what runs. Give the pipeline credentials with only the permissions it needs, and separate environments by state and by account where possible. Use prevent_destroy on resources whose loss would be serious, such as databases, and read the plan for lines that say a resource will be replaced, because a replacement destroys then recreates and can mean downtime or data loss. Start small: manage one component, learn how the plan behaves and expand once the workflow is routine.

Learn the destroy side as carefully as the create side. terraform destroy removes everything in the state, and for a shared environment that is rarely what anyone wants. Use targeted plans sparingly, since they skip parts of the dependency graph and can leave the state inconsistent. Run terraform plan -destroy to see what a teardown would remove before any real use, and tag resources with the project and owner so stray objects can be traced. For short-lived test environments, building and destroying from the same configuration is the main benefit, and a nightly teardown keeps the cloud bill honest.

Where this does not apply

For one server that changes twice a year, Terraform is more machinery than value. It declares infrastructure poorly suited to ordering-heavy tasks such as running a migration, which belongs in a script or pipeline. Licence changes in 2023 led to the OpenTofu fork, which uses the same configuration language; check which one your organisation standardises on. Destroying and recreating is the default for some changes, so read plan output for the words "must be replaced". See the Terraform language documentation and the OpenTofu documentation.

Also inDevOps
Taggedterraform
Share

Keep reading

Get new posts by email

One email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.