Skip to content
Commitpedia
Log in Join & edit

First Terraform configuration: variables, a module and outputs (no cloud needed)

A complete Terraform project that writes local files with the hashicorp/local provider, so you can practice init, plan, apply, modules and destroy safely without a cloud account.

Edit history of First Terraform configuration: variables, a module and outputs (no cloud needed): 1 recorded edit
Edit history · 1 edit
Type
Script
Edits
1
Editors
1
First issued
15 Oct 2026
Last edit
15 Oct 2026
Language
hcl
Licence
CC0-1.0

Learning Terraform against a real cloud costs money and can leave stray resources behind. The hashicorp/local provider manages files on your own disk, which makes it a safe sandbox: the workflow, state, variables and modules behave exactly as they do with cloud providers. The same files work with OpenTofu; replace terraform with tofu in the commands.

Project layout

tf-sandbox/
  main.tf
  modules/
    note/
      main.tf

main.tf (root module)

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

variable "environment" {
  description = "Name of the environment, used in file names"
  type        = string
  default     = "dev"
}

variable "servers" {
  description = "Hostnames that should appear in the generated inventory"
  type        = list(string)
  default     = ["web1.example.com", "web2.example.com"]
}

resource "local_file" "inventory" {
  filename        = "${path.module}/out/${var.environment}-inventory.ini"
  content         = "[web]\n${join("\n", var.servers)}\n"
  file_permission = "0644"
}

module "readme" {
  source = "./modules/note"

  file_path = "${path.module}/out/${var.environment}-README.txt"
  text      = "Generated by Terraform for the ${var.environment} environment."
}

output "inventory_file" {
  value = local_file.inventory.filename
}

output "readme_file" {
  value = module.readme.file_path
}

modules/note/main.tf (child module)

terraform {
  required_providers {
    local = {
      source = "hashicorp/local"
    }
  }
}

variable "file_path" {
  description = "Where to write the note"
  type        = string
}

variable "text" {
  description = "Content of the note"
  type        = string
}

resource "local_file" "this" {
  filename        = var.file_path
  content         = "${var.text}\n"
  file_permission = "0644"
}

output "file_path" {
  value = local_file.this.filename
}

The child module declares its own inputs (file_path, text) and an output. The root module passes values in through the module block and reads the result as module.readme.file_path. path.module is the folder of the module the expression is written in.

Run it step by step

  1. terraform init downloads the local provider, installs the module and creates .terraform.lock.hcl.
  2. terraform fmt and terraform validate format the code and check it for errors.
  3. terraform plan -out=tfplan shows two resources to add (+) and saves the plan to a file.
  4. terraform apply tfplan applies exactly the saved plan, without asking again. The files appear under out/ and the outputs are printed.
  5. Run terraform plan again: it reports no changes, because state and reality match the code.

Make changes

terraform plan -var 'environment=test'
terraform apply -var 'servers=["web1.example.com","web2.example.com","web3.example.com"]'
terraform state list
terraform output inventory_file

Changing environment changes the file names, so the plan shows -/+: the old files are replaced by new ones. Changing servers only changes the content, which for this resource also means replace. terraform state list shows the addresses Terraform tracks, such as local_file.inventory and module.readme.local_file.this.

Delete one of the generated files by hand and run terraform plan: Terraform notices the object is gone and plans to create it again. This is drift detection.

Clean up

Warning: destroy deletes every resource in the state. Here that is only the generated files, but in a real project it means servers and data. Always preview first with terraform plan -destroy and check which project folder and backend you are in.

terraform plan -destroy
terraform destroy

Do not commit terraform.tfstate, tfplan or the .terraform/ folder to Git. Do commit the .tf files and .terraform.lock.hcl.

Official documentation

See also

  • 0187 Terraform concepts: providers, state, plan and apply, modules, and OpenTofu Terraform describes infrastructure in code, compares it with what exists, and changes only the difference. Learn providers, resources, state, the plan/apply loop,… Beginner · Concept · Any system Article Terraform 15 Oct 2026
  • 0189 Terraform remote state: backends, locking and migrating local state Move Terraform state from a local file into a shared backend such as S3 with locking, so a team can work safely.… Advanced · How-to · Any system Article Terraform 15 Oct 2026
  • 0190 Fix: Terraform Error acquiring the state lock Another run holds the lock on your Terraform state, or a crashed run left it behind. Find out who holds it, wait… Advanced · Fix · Any system Article Terraform 15 Oct 2026
  • 0256 Manage infrastructure as code with Terraform: from first file to plans on pull requests Learn the full Terraform workflow without a cloud account: variables, a module, outputs, state, plan, apply and destroy, a backend for shared… Beginner · Tutorial · Linux, macOS, Windows Article GitHub Actions · Terraform 15 Oct 2026

Repos in the wild

History

Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.

  1. commitpedia-admin +4,266 chars

Report a problem

Something wrong, unsafe or copied? Tell the editors.
What is wrong?