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.
Not checked by a trusted editor yet Last edited Outdated?
What this changes
terraform init downloads the hashicorp/local provider into .terraform/ and writes .terraform.lock.hcl. apply creates out/ENV-inventory.ini, out/ENV-README.txt and terraform.tfstate in the tf-sandbox folder. No cloud resources are created.
How to undo it
Run terraform destroy to delete the files in out/, then rm -rf .terraform .terraform.lock.hcl terraform.tfstate*. Or delete the whole tf-sandbox folder.
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
terraform initdownloads the local provider, installs the module and creates.terraform.lock.hcl.terraform fmtandterraform validateformat the code and check it for errors.terraform plan -out=tfplanshows two resources to add (+) and saves the plan to a file.terraform apply tfplanapplies exactly the saved plan, without asking again. The files appear underout/and the outputs are printed.- Run
terraform planagain: 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
- OpenTofu: open-source infrastructure as code opentofu/opentofu OpenTofu is the community fork of Terraform under the MPL-2.0 license, run as a Linux Foundation project. It uses the same configuration language and providers. GitHub
History
Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.
- Commitpedia +4,266 chars