Objective
By the end of this lab, you will have:
- Installed the Terraform CLI and verified the version.
- Written a first Terraform configuration that uses the
hashicorp/localprovider to create a file on disk. - Run the daily workflow:
init,validate,plan,apply,destroy. - Inspected the resulting state file.
- Verified the cleanup.
The lab uses the local provider, which is a provider that
creates files on the local filesystem. No cloud account is
required. The lab is disposable: every step has a corresponding
cleanup step.
Architecture
A single Terraform configuration that creates one file on the local filesystem:
+-----------------------+
| Terraform |
| ↓ |
| hashicorp/local |
| ↓ |
| local_file.hello.txt |
| ↓ |
| $HOME/rb-academy-lab/ |
| └── hello.txt |
+-----------------------+
Requirements
- A Linux or macOS workstation with shell access.
- The Terraform CLI 1.9.x or later installed.
- A writable directory (e.g. under
$HOME).
Verify the Terraform version:
terraform version
Expected output (truncated):
Terraform v1.9.x
on linux_amd64
If the version is older, see the Terraform install guide.
Scenario
You have a fresh workstation. You want to create a file
hello.txt in a working directory. You have decided to manage
the file with Terraform as a first exercise.
The file is empty. The configuration is small. The lesson is the workflow, not the resource.
Tasks
Task 1: Create the working directory
mkdir -p ~/rb-academy-lab
cd ~/rb-academy-lab
Verify the directory is empty:
ls -la ~/rb-academy-lab
Task 2: Write the configuration
Create a file main.tf:
terraform {
required_version = ">= 1.9.0"
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
resource "local_file" "hello" {
filename = "${path.module}/hello.txt"
content = "Hello from Terraform!\n"
}
The configuration:
- Pins Terraform to 1.9.x or later.
- Pins the
localprovider to the 2.5.x series. - Declares one resource,
local_file.hello, which creates a filehello.txtin the working directory.
Task 3: Format the configuration
terraform fmt
Verify the file is unchanged (the configuration is already formatted):
git diff main.tf
Task 4: Initialise the working directory
terraform init
The output:
Initializing the backend...
Initializing provider plugins...
- Installing hashicorp/local v2.5.x...
- Installed hashicorp/local v2.5.x
Terraform has been successfully initialized!
What init did:
- Created a
.terraform/directory. - Downloaded the
hashicorp/localprovider. - Created a
.terraform.lock.hclfile with the lock.
Verify:
ls -la ~/rb-academy-lab
Expected:
.terraform/
.terraform.lock.hcl
main.tf
Task 5: Validate the configuration
terraform validate
Expected output:
Success! The configuration is valid.
The validation is a sanity check. The actual resource creation happens in the apply step.
Task 6: Plan the apply
terraform plan
Expected output:
Terraform will perform the following actions:
# local_file.hello will be created
+ resource "local_file" "hello" {
+ content = "Hello from Terraform!\n"
+ content_base64 = (known after apply)
+ directory_permission = "0777"
+ file_permission = "0777"
+ filename = "./hello.txt"
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Study the plan output:
- The plan proposes to create one resource.
- The
contentargument matches the configuration. - The
idis(known after apply)— the provider will assign it during the apply. - The
file_permissionanddirectory_permissionare defaults (the providers0777).
Task 7: Save the plan
terraform plan -out=hello.tfplan
The plan is saved to hello.tfplan. The file is in the
working directory.
Verify the file exists:
ls -la hello.tfplan
Task 8: Inspect the saved plan
terraform show hello.tfplan
The output is the same as the plan output but read from the saved file.
terraform show -json hello.tfplan | head -50
The JSON output is machine-readable. The CI pipeline can parse it.
Task 9: Apply the saved plan
terraform apply hello.tfplan
The output:
local_file.hello: Creating...
local_file.hello: Creation complete after 0s [id=abc123def456]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
What apply did:
- Created the file
hello.txt. - Updated the state file.
- Saved the state file.
Verify the file exists:
cat ~/rb-academy-lab/hello.txt
Expected:
Hello from Terraform!
Task 10: Inspect the state
terraform state list
Expected:
local_file.hello
The state has one resource.
terraform state show local_file.hello
Expected:
# local_file.hello:
resource "local_file" "hello" {
content = "Hello from Terraform!\n"
content_base64 = "SGVsbG8gZnJvbSBUZXJyYWZvcm0hCg=="
directory_permission = "0777"
file_permission = "0777"
filename = "./hello.txt"
id = "abc123def456"
}
The state records every attribute the provider returned.
terraform show
The output is a human-readable summary of the state.
Task 11: Verify idempotency
terraform plan
Expected output:
No changes. Your infrastructure matches the configuration.
The plan is empty. The configuration matches the state.
Task 12: Apply without a saved plan
terraform apply
The plan is computed at apply time. The plan is empty. The apply is a no-op.
The output:
No changes. Your infrastructure matches the configuration.
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Task 13: Modify the resource
Edit main.tf to change the content:
resource "local_file" "hello" {
filename = "${path.module}/hello.txt"
content = "Hello from Terraform! Updated.\n"
}
Run the plan:
terraform plan
Expected output:
# local_file.hello will be updated in-place
~ resource "local_file" "hello" {
~ content = "Hello from Terraform!\n" -> "Hello from Terraform! Updated.\n"
~ content_base64 = "SGVsbG8gZnJvbSBUZXJyYWZvcm0hCg==" -> "SGVsbG8gZnJvbSBUZXJyYWZvcm0hIFVwZGF0ZWQuCg=="
# (5 unchanged attributes hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
The plan proposes to update the resource in place. The
content attribute changes; the other attributes are unchanged.
Apply:
terraform apply
Verify:
cat ~/rb-academy-lab/hello.txt
Expected:
Hello from Terraform! Updated.
Task 14: Destroy the resource
terraform destroy
The apply is unconfirmed:
Plan: 0 to add, 0 to change, 1 to destroy.
local_file.hello: Refreshing state... [id=abc123def456]
# local_file.hello will be destroyed
- resource "local_file" "hello" {
- content = "Hello from Terraform! Updated.\n" -> null
- content_base64 = "SGVsbG8gZnJvbSBUZXJyYWZvcm0hIFVwZGF0ZWQuCg==" -> null
- directory_permission = "0777" -> null
- file_permission = "0777" -> null
- filename = "./hello.txt" -> null
- id = "abc123def456" -> null
}
Do you really want to destroy all resources?
Terraform will destroy all the resources described above, all the
data they contain, and all the provider configurations.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
Type yes to confirm.
The output:
local_file.hello: Destroying... [id=abc123def456]
local_file.hello: Destruction complete after 0s
Destroy complete! Resources: 0 destroyed.
Verify the file is gone:
ls -la ~/rb-academy-lab
Expected:
.terraform/
.terraform.lock.hcl
hello.tfplan
main.tf
The hello.txt file is gone. The state is empty.
Validation
The lab is successful if:
- The Terraform CLI version is 1.9.x or later.
- The
hello.txtfile is created and removed by Terraform. - The state file matches the configuration.
- The plan is empty after the apply.
- The destroy removes the file.
Expected Outcome
At the end of the lab:
+---------------------------------+
| ~/rb-academy-lab/ |
| .terraform/ |
| .terraform.lock.hcl |
| hello.tfplan |
| main.tf |
| terraform.tfstate |
+---------------------------------+
The hello.txt file is gone. The state is empty. The
configuration is the only source of truth for what Terraform
believes exists.
Troubleshooting
terraform init fails with provider download error
Error: Failed to install provider
Verify the network connection:
curl -fsSL https://registry.terraform.io/.well-known/terraform.json
If the registry is unreachable, check the corporate firewall or proxy settings.
terraform apply fails with permission denied
The local provider writes to the current working directory.
If the working directory is unwritable, the apply fails.
ls -ld ~/rb-academy-lab
chmod 755 ~/rb-academy-lab
The state file is locked
A previous apply may have crashed. The lock file is in the
.terraform/ directory. Delete the lock file and try again:
rm -rf .terraform/
terraform init
Cleanup
The end state is a working directory with the configuration and the state. To fully clean up:
cd ~/rb-academy-lab
terraform destroy # if any resources remain
rm -rf .terraform .terraform.lock.hcl hello.tfplan terraform.tfstate
The main.tf is the only artefact worth keeping. Move it to a
permanent location (e.g. ~/rb-academy-configs/first-terraform/).
What You Learned
You learned the daily Terraform workflow:
terraform fmtenforces standard formatting.terraform initdownloads providers and prepares the working directory.terraform validateperforms static checks.terraform planproduces the operational artefact.terraform applymakes the plan real.terraform destroycleanly removes the resources.
You also learned:
- The state file is the source of truth for what Terraform believes exists.
- The idempotency of the plan is the safety net.
- The save-plan workflow is the production pattern.
- The
.terraform.lock.hclis the reproducibility contract.