Automating the construction and operation of cloud environments is where Terraform truly shines.
In this article, we’ll organize Terraform’s fundamental concepts and usage, and then dive into one of the most common stumbling blocks: the difference between variable
and locals
. We’ll explain this in comparison to scoping in JavaScript, with concrete AWS examples so you can easily picture real-world usage.
Why use Terraform?
Traditionally, when building AWS infrastructure manually through the console or CLI, teams often ran into the following issues:
- Human error: manual operations are prone to mistakes
- Lack of reproducibility: it’s hard to quickly rebuild the same environment for disaster recovery or new deployments
- Knowledge silos: build procedures often live only in someone’s head
- Scaling limitations: managing large-scale environments manually is not feasible
- Opaque change management: difficult to track who changed what, and when
The solution is Infrastructure as Code (IaC): defining infrastructure in code, enabling reproducibility, automation, and auditability of change history. Terraform is one of the most widely used tools for this.
Key features of Terraform
- Multi-cloud support: not only AWS, Azure, and GCP, but also Kubernetes, GitHub, and many other providers
- Declarative syntax: you declare what you want to build, and Terraform resolves dependencies automatically
-
State management: tracks infrastructure state in
terraform.tfstate
, ensuring differences between code and reality are clear - Modularization: encapsulate common setups as modules for reuse
Core building blocks
Here are the main constructs you need to understand Terraform:
Element | Role |
---|---|
provider | Configures the cloud or service you’ll use (e.g., AWS region, credentials) |
resource | Defines the infrastructure resources (e.g., EC2, S3, VPC) |
data | References existing resources |
variable | Accepts values from outside Terraform |
locals | Defines reusable values inside the code |
module | A reusable collection of resources |
output | Exposes values externally (e.g., an EC2 public IP) |
Diving deeper into variable and locals
Key differences
-
variable
- The entry point for external input
- Accepts values from CLI flags, environment variables, or tfvars files
- Used to handle differences between environments (region, env name, instance type, etc.)
-
locals
- Values for reuse inside the code
- Cannot be overridden externally
- Improves readability by centralizing common tags or naming rules
Example in AWS
# variables.tf
variable "region" {
description = "AWS region"
type = string
default = "ap-northeast-1"
}
variable "env" {
description = "Environment name"
type = string
default = "dev"
}
# locals.tf
locals {
common_tags = {
Project = "sample-app"
Environment = var.env
}
}
# s3.tf
provider "aws" {
region = var.region
}
resource "aws_s3_bucket" "this" {
bucket = "${var.env}-app-bucket"
tags = local.common_tags
}
👉 In this example:
-
region
andenv
can be switched externally -
common_tags
is an internal value reused across resources
Difference from scoping in languages like JavaScript or Python
This is a common point of confusion.
In JavaScript or Python, a variable defined inside a function cannot be referenced outside of it. Terraform’s locals
, however, can be referenced across files within the same module (module scope).
Referencing within a module
# locals.tf
locals {
prefix = "demo"
}
# ec2.tf
resource "aws_instance" "this" {
ami = "ami-123456"
instance_type = "t2.micro"
tags = {
Name = "${local.prefix}-server"
}
}
👉 Even if the files are separate, they belong to the same module, so local.prefix
is available everywhere inside that module.
Referencing across modules
However, you cannot reference locals directly from another module.
In such cases, you need to expose values via outputs.
# modules/network/locals.tf
locals {
vpc_cidr = "10.0.0.0/16"
}
output "vpc_cidr" {
value = local.vpc_cidr
}
# main.tf
module "network" {
source = "./modules/network"
}
resource "aws_subnet" "this" {
cidr_block = module.network.vpc_cidr
}
👉 Remember: locals are module-scoped, not file-scoped.
Essential Terraform commands
Command | Description |
---|---|
terraform init |
Initializes the project and downloads provider plugins |
terraform plan |
Shows a preview of changes before applying |
terraform apply |
Applies the changes and builds infrastructure |
terraform destroy |
Destroys managed resources |
terraform fmt |
Formats the code |
terraform validate |
Validates syntax |
terraform output |
Displays output values |
terraform state |
Inspects and manages the state file |
Example project structure (with modules)
.
├── environments
│ ├── dev
│ │ └── main.tf
│ └── prod
│ └── main.tf
└── modules
└── nginx
├── main.tf
├── variables.tf
└── locals.tf
-
environments/dev
andenvironments/prod
absorb environment-specific differences -
modules/nginx
encapsulates common logic - Use
variable
for environment-dependent values - Use
locals
to centralize common handling (tags, naming rules)
Best practices
- Manage state files remotely (e.g., S3 with DynamoDB lock, or Terraform Cloud)
- Pin provider and Terraform versions
- Split modules by responsibility for clarity
- Centralize naming/tagging rules with
locals
- Automate
terraform fmt
,validate
, andplan
checks in CI/CD
Conclusion
- Terraform is a powerful tool for managing infrastructure as code
-
variable
is the entry point for external input -
locals
centralizes shared internal values - Scoping is module-level: accessible across files within a module, but requires outputs when crossing module boundaries
- Unlike JavaScript’s function scope, Terraform’s locals follow module scope, which makes reuse across files easier
✅ Would you like me to also make this “polished blog-ready version” with a more natural, engaging tone (like for Medium/Zenn) — or keep it as a straightforward technical translation?
Top comments (0)