π Hey there, tech enthusiasts!
I'm Sarvar, a Cloud Architect with a passion for transforming complex technological challenges into elegant solutions. With extensive experience spanning Cloud Operations (AWS & Azure), Data Operations, Analytics, DevOps, and Generative AI, I've had the privilege of architecting solutions for global enterprises that drive real business impact. Through this article series, I'm excited to share practical insights, best practices, and hands-on experiences from my journey in the tech world. Whether you're a seasoned professional or just starting out, I aim to break down complex concepts into digestible pieces that you can apply in your projects.
"The difference between a script and reusable infrastructure code is variables."
π― Welcome Back!
Remember in Article 3 when you created that S3 bucket? You hardcoded the bucket name directly in the code:
resource "aws_s3_bucket" "my_first_bucket" {
bucket = "terraform-first-bucket-yourname-2026" # Hardcoded!
}
That works... once. But what if you need:
- 10 buckets with different names?
- Same code for dev, staging, and production?
- To let your team customize configurations?
- To reuse this code in different projects?
Today, you'll learn how to make your Terraform code flexible and reusable using variables and outputs.
By the end of this article, you'll:
- β Convert hardcoded values to variables
- β Use all variable types (string, number, bool, list, map)
- β
Work with
terraform.tfvarsfiles - β Create outputs to expose information
- β Build dev/prod configurations from the same code
- β Use variable validation and sensitive values
Time Required: 30 minutes
Cost: $0 (using free tier resources)
Difficulty: Beginner-friendly
Let's make your code reusable! π
π The Problem: Hardcoded Hell
The Painful Reality
Scenario 1: Multiple Environments
# dev.tf
resource "aws_s3_bucket" "app" {
bucket = "myapp-dev-bucket"
}
# staging.tf (copy-paste and change)
resource "aws_s3_bucket" "app" {
bucket = "myapp-staging-bucket"
}
# prod.tf (copy-paste and change again)
resource "aws_s3_bucket" "app" {
bucket = "myapp-prod-bucket"
}
Problems:
- β Three copies of the same code
- β Change one thing = update three files
- β High risk of mistakes
- β Nightmare to maintain
Scenario 2: Team Collaboration
Developer 1: "I need the bucket name to be X"
Developer 2: "I need it to be Y"
DevOps: "Production needs Z"
Everyone edits the same .tf file = merge conflicts and chaos!
There's a better way. Enter variables.
π What Are Variables?
Simple Definition
Variables are like function parameters for your infrastructure code.
Think of it like this:
Without Variables (Hardcoded):
function createBucket() {
return "my-hardcoded-bucket-name";
}
With Variables (Flexible):
function createBucket(bucketName) {
return bucketName;
}
createBucket("dev-bucket"); // Different uses
createBucket("prod-bucket"); // Same code!
In Terraform:
# Define the variable
variable "bucket_name" {
description = "Name of the S3 bucket"
type = string
}
# Use the variable
resource "aws_s3_bucket" "app" {
bucket = var.bucket_name # Reference with var.
}
Same code, different values! That's the power of variables.
π οΈ Hands-On: Your First Variable
Let's convert our hardcoded S3 bucket to use variables.
Step 1: Create Project Directory
mkdir -p ~/terraform-variables-demo
cd ~/terraform-variables-demo
Step 2: Create variables.tf
Create a new file called variables.tf:
nano variables.tf
Add this code:
# String variable - for text values
variable "aws_region" {
description = "AWS region where resources will be created"
type = string
default = "us-east-1"
}
variable "bucket_name" {
description = "Name of the S3 bucket (must be globally unique)"
type = string
# No default - user must provide this value
}
# Map variable - for key-value pairs like tags
variable "common_tags" {
description = "Common tags to apply to all resources"
type = map(string)
default = {
Environment = "Development"
ManagedBy = "Terraform"
Project = "Learning"
}
}
Understanding the syntax:
-
description- Explains what the variable is for (always add this!) -
type- What kind of value (string, number, bool, list, map) -
default- Optional default value (if not provided, user must supply it)
Step 3: Create main.tf
nano main.tf
Add this code:
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Use variable in provider
provider "aws" {
region = var.aws_region # Reference variable with var.
}
# Use variables in resource
resource "aws_s3_bucket" "example" {
bucket = var.bucket_name # Variable for bucket name
# Merge common_tags with specific tags
tags = merge(
var.common_tags,
{
Name = var.bucket_name
}
)
}
Notice: We reference variables with var.variable_name
Step 4: Initialize and Test
terraform init
terraform plan
Terraform will prompt you:
var.bucket_name
Name of the S3 bucket (must be globally unique)
Enter a value:
Type: my-first-variable-bucket-yourname-2026 and press Enter.
You'll see the plan:
Terraform will perform the following actions:
# aws_s3_bucket.example will be created
+ resource "aws_s3_bucket" "example" {
+ bucket = "my-first-variable-bucket-yourname-2026"
+ tags = {
+ "Environment" = "Development"
+ "ManagedBy" = "Terraform"
+ "Name" = "my-first-variable-bucket-yourname-2026"
+ "Project" = "Learning"
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
Don't apply yet! Let's learn better ways to pass variables.
π Three Ways to Pass Variables
Method 1: Command Line (What We Just Did)
terraform plan
# Terraform prompts for bucket_name
Pros: Interactive
Cons: Tedious for multiple variables
Method 2: Command Line Flags
terraform plan -var="bucket_name=my-bucket-2026"
Pros: Good for CI/CD pipelines
Cons: Long commands with many variables
Method 3: terraform.tfvars File (Best for Most Cases)
Create terraform.tfvars:
nano terraform.tfvars
Add this:
bucket_name = "my-variable-bucket-yourname-2026"
aws_region = "us-east-1"
common_tags = {
Environment = "Development"
ManagedBy = "Terraform"
Project = "Variables Demo"
Owner = "YourName"
}
Now run:
terraform plan
No prompts! Terraform automatically loads terraform.tfvars.
Apply it:
terraform apply
Type yes when prompted.
π Success! You just created infrastructure using variables!
π¨ Variable Types: The Complete Guide
Terraform supports multiple variable types. Let's explore them all!
Update variables.tf
Replace your variables.tf with this comprehensive version:
# 1. STRING - Text values
variable "aws_region" {
description = "AWS region where resources will be created"
type = string
default = "us-east-1"
}
variable "bucket_name" {
description = "Name of the S3 bucket (must be globally unique)"
type = string
}
variable "bucket_prefix" {
description = "Prefix for multiple bucket names"
type = string
default = "terraform-demo"
}
# 2. NUMBER - Numeric values
variable "bucket_count" {
description = "Number of S3 buckets to create"
type = number
default = 2
# Validation rule
validation {
condition = var.bucket_count >= 1 && var.bucket_count <= 5
error_message = "Bucket count must be between 1 and 5."
}
}
# 3. BOOLEAN - True/False values
variable "enable_versioning" {
description = "Enable versioning on the S3 bucket"
type = bool
default = false
}
# 4. LIST - Ordered collection of values
variable "allowed_ips" {
description = "List of IP addresses allowed to access the bucket"
type = list(string)
default = []
}
# 5. MAP - Key-value pairs
variable "common_tags" {
description = "Common tags to apply to all resources"
type = map(string)
default = {
Environment = "Development"
ManagedBy = "Terraform"
Project = "Learning"
}
}
# 6. ENVIRONMENT - With validation
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
default = "dev"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
Update main.tf to Use All Variable Types
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# Main S3 bucket with variables
resource "aws_s3_bucket" "example" {
bucket = var.bucket_name
tags = merge(
var.common_tags,
{
Name = var.bucket_name
}
)
}
# Conditional resource - only created if enable_versioning is true
resource "aws_s3_bucket_versioning" "example" {
count = var.enable_versioning ? 1 : 0 # Conditional creation
bucket = aws_s3_bucket.example.id
versioning_configuration {
status = "Enabled"
}
}
# Multiple buckets using count (number variable)
resource "aws_s3_bucket" "multiple" {
count = var.bucket_count
bucket = "${var.bucket_prefix}-${count.index + 1}-yourname-2026"
tags = var.common_tags
}
What's happening here:
-
var.enable_versioning ? 1 : 0- Conditional: if true, create 1 resource; if false, create 0 -
count = var.bucket_count- Create multiple resources based on number -
count.index- Access the current index (0, 1, 2...) -
merge()- Combine two maps together
π€ Outputs: Exposing Information
Variables let data IN. Outputs let data OUT.
Why Outputs Matter
Use cases:
- Display important information after
terraform apply - Pass values to other Terraform modules
- Use in scripts or CI/CD pipelines
- Share information with team members
Create outputs.tf
nano outputs.tf
Add this code:
# Basic outputs
output "bucket_name" {
description = "Name of the created S3 bucket"
value = aws_s3_bucket.example.id
}
output "bucket_arn" {
description = "ARN of the S3 bucket"
value = aws_s3_bucket.example.arn
}
output "bucket_region" {
description = "Region where the bucket was created"
value = aws_s3_bucket.example.region
}
output "bucket_domain_name" {
description = "Domain name of the bucket"
value = aws_s3_bucket.example.bucket_domain_name
}
# Multiple buckets output
output "multiple_bucket_names" {
description = "Names of all created buckets"
value = aws_s3_bucket.multiple[*].id
}
# Conditional output
output "versioning_enabled" {
description = "Whether versioning is enabled"
value = var.enable_versioning
}
# Map output
output "bucket_tags" {
description = "Tags applied to the bucket"
value = aws_s3_bucket.example.tags
}
# Sensitive output example
output "bucket_id_sensitive" {
description = "Bucket ID (marked as sensitive)"
value = aws_s3_bucket.example.id
sensitive = true # Won't display in plan/apply output
}
Update terraform.tfvars
bucket_name = "my-variable-bucket-yourname-2026"
bucket_prefix = "demo-bucket-yourname"
bucket_count = 2
enable_versioning = true
aws_region = "us-east-1"
common_tags = {
Environment = "Development"
ManagedBy = "Terraform"
Project = "Variables Demo"
Owner = "YourName"
}
Apply and See Outputs
terraform apply
After applying, you'll see:
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
Outputs:
bucket_arn = "arn:aws:s3:::my-variable-bucket-yourname-2026"
bucket_domain_name = "my-variable-bucket-yourname-2026.s3.amazonaws.com"
bucket_id_sensitive = <sensitive>
bucket_name = "my-variable-bucket-yourname-2026"
bucket_region = "us-east-1"
bucket_tags = tomap({
"Environment" = "Development"
"ManagedBy" = "Terraform"
"Name" = "my-variable-bucket-yourname-2026"
"Owner" = "YourName"
"Project" = "Variables Demo"
})
multiple_bucket_names = [
"demo-bucket-yourname-1-yourname-2026",
"demo-bucket-yourname-2-yourname-2026",
]
versioning_enabled = true
View Outputs Anytime
# View all outputs
terraform output
# View specific output
terraform output bucket_name
# View sensitive output (will reveal the value)
terraform output bucket_id_sensitive
# Output as JSON (useful for scripts)
terraform output -json
π Real-World: Dev vs Prod Configurations
The real power of variables: same code, different configurations!
Create dev.tfvars
nano dev.tfvars
# Development environment configuration
environment = "dev"
bucket_name = "myapp-dev-yourname-2026"
bucket_prefix = "myapp-dev-yourname"
bucket_count = 2
enable_versioning = false # Save costs in dev
aws_region = "us-east-1"
common_tags = {
Environment = "Development"
ManagedBy = "Terraform"
Project = "MyApp"
CostCenter = "Engineering"
}
Create prod.tfvars
nano prod.tfvars
# Production environment configuration
environment = "prod"
bucket_name = "myapp-prod-yourname-2026"
bucket_prefix = "myapp-prod-yourname"
bucket_count = 3
enable_versioning = true # Important for prod!
aws_region = "us-east-1"
common_tags = {
Environment = "Production"
ManagedBy = "Terraform"
Project = "MyApp"
CostCenter = "Operations"
Compliance = "Required"
}
Deploy to Dev
terraform plan -var-file="dev.tfvars"
terraform apply -var-file="dev.tfvars"
Result: 3 buckets created (1 main + 2 multiple), no versioning
Deploy to Prod
# First destroy dev
terraform destroy -var-file="dev.tfvars"
# Then deploy prod
terraform plan -var-file="prod.tfvars"
terraform apply -var-file="prod.tfvars"
Result: 4 buckets created (1 main + 3 multiple), versioning enabled
Same code, different results! This is how professionals manage multiple environments.
β Best Practices
1. Always Add Descriptions
Bad:
variable "name" {
type = string
}
Good:
variable "bucket_name" {
description = "Name of the S3 bucket for application logs (must be globally unique)"
type = string
}
2. Use Validation Rules
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
validation {
condition = contains(["t2.micro", "t2.small", "t3.micro"], var.instance_type)
error_message = "Instance type must be t2.micro, t2.small, or t3.micro."
}
}
3. Provide Sensible Defaults
variable "aws_region" {
description = "AWS region"
type = string
default = "us-east-1" # Most common region
}
4. Use Meaningful Variable Names
Bad: var.n, var.x, var.thing
Good: var.bucket_name, var.instance_count, var.enable_monitoring
5. Group Related Variables
# Network variables
variable "vpc_cidr" { }
variable "subnet_cidrs" { }
# Application variables
variable "app_name" { }
variable "app_version" { }
# Tags
variable "common_tags" { }
6. Never Commit Secrets to tfvars
Bad:
# terraform.tfvars
database_password = "super-secret-password" # DON'T DO THIS!
Good:
# Use environment variables for secrets
export TF_VAR_database_password="super-secret-password"
Or use AWS Secrets Manager, HashiCorp Vault, etc.
7. Document Your Variables
Create a README.md:
## Required Variables
- `bucket_name` - Globally unique S3 bucket name
## Optional Variables
- `aws_region` - AWS region (default: us-east-1)
- `enable_versioning` - Enable bucket versioning (default: false)
π Common Issues and Solutions
Issue 1: Variable Not Found
Error:
Error: Reference to undeclared input variable
Solution: Make sure variable is declared in variables.tf:
variable "bucket_name" {
description = "Bucket name"
type = string
}
Issue 2: Type Mismatch
Error:
Error: Invalid value for input variable
Solution: Check your variable type matches the value:
variable "bucket_count" {
type = number # Not string!
}
In terraform.tfvars:
bucket_count = 2 # Not "2" (string)
Issue 3: Validation Failed
Error:
Error: Invalid value for variable
Bucket count must be between 1 and 5.
Solution: Provide a value that meets the validation condition:
bucket_count = 3 # Within 1-5 range
Issue 4: Forgot to Pass tfvars File
Problem: Terraform uses default values instead of your custom file
Solution: Always specify the file:
terraform apply -var-file="prod.tfvars"
Issue 5: Sensitive Output Not Hidden
Problem: Sensitive data showing in output
Solution: Mark output as sensitive:
output "database_password" {
value = var.db_password
sensitive = true # Add this
}
π What You Just Learned
Let's recap your new superpowers:
β Variables:
- Convert hardcoded values to flexible variables
- Use all variable types (string, number, bool, list, map)
- Add validation rules
- Provide defaults
β terraform.tfvars:
- Store variable values in files
- Create environment-specific configs (dev.tfvars, prod.tfvars)
- Auto-loading behavior
β Outputs:
- Display important information
- Mark sensitive data
- Use in scripts and modules
β Real-World Patterns:
- Same code, multiple environments
- Conditional resource creation
- Multiple resource creation with count
π― Challenge: Try It Yourself
Before moving to the next article, try these challenges:
Challenge 1: Add More Variables
Add variables for:
owner_emailcost_centerbackup_retention_days
Challenge 2: Create staging.tfvars
Create a staging environment configuration between dev and prod.
Challenge 3: Use List Variable
Create a variable for multiple availability zones:
variable "availability_zones" {
type = list(string)
default = ["us-east-1a", "us-east-1b"]
}
Challenge 4: Complex Map Variable
Create a variable for instance configurations:
variable "instance_configs" {
type = map(object({
instance_type = string
volume_size = number
}))
}
Challenge 5: Output Formatting
Create an output that combines multiple values:
output "bucket_info" {
value = "Bucket ${aws_s3_bucket.example.id} in ${var.aws_region}"
}
π Additional Variable Features
Variable Precedence (Order of Priority)
Terraform loads variables in this order (last wins):
- Environment variables (
TF_VAR_name) -
terraform.tfvarsfile -
terraform.tfvars.jsonfile -
*.auto.tfvarsfiles (alphabetical order) -
-varand-var-filecommand line flags
Example:
# All of these set the same variable
export TF_VAR_bucket_name="from-env" # Priority 1
# terraform.tfvars: bucket_name = "from-file" # Priority 2
terraform apply -var="bucket_name=from-cli" # Priority 3 (wins!)
Environment Variables
# Set variable via environment
export TF_VAR_bucket_name="my-bucket-2026"
export TF_VAR_aws_region="us-west-2"
terraform plan # Uses environment variables
Auto-Loading tfvars Files
Terraform automatically loads:
terraform.tfvarsterraform.tfvars.json*.auto.tfvars*.auto.tfvars.json
Example:
# These are auto-loaded
terraform.tfvars
common.auto.tfvars
secrets.auto.tfvars
# These need -var-file flag
dev.tfvars
prod.tfvars
π Understanding Variable Interpolation
You can use variables in many ways:
String Interpolation
resource "aws_s3_bucket" "example" {
bucket = "${var.environment}-${var.app_name}-bucket"
# Result: "dev-myapp-bucket"
}
Conditional Expressions
resource "aws_instance" "web" {
instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"
# prod = t3.large, anything else = t3.micro
}
For Expressions
locals {
bucket_names = [for i in range(var.bucket_count) : "${var.prefix}-${i}"]
# Result: ["app-0", "app-1", "app-2"]
}
π Useful Resources
- Terraform Variables Docs: https://developer.hashicorp.com/terraform/language/values/variables
- Terraform Outputs Docs: https://developer.hashicorp.com/terraform/language/values/outputs
- Variable Validation: https://developer.hashicorp.com/terraform/language/values/variables#custom-validation-rules
- Type Constraints: https://developer.hashicorp.com/terraform/language/expressions/type-constraints
π¬ What's Next?
In Article 6: Building a VPC from Scratch, we'll:
- Create a complete AWS VPC with subnets
- Use variables for network configuration
- Build public and private subnets
- Configure route tables and internet gateways
- Apply everything you learned about variables
You'll learn:
- Real-world networking with Terraform
- Complex resource dependencies
- Advanced variable usage
- Production-ready VPC architecture
π Wrapping Up
Thank you for reading. I hope this article provided practical insights and a clearer understanding of the topic.
If you found this useful:
- β€οΈ Like if it added value
- π¦ Unicorn if youβre applying it today
- πΎ Save it for your next optimization session
- π Share it with your team
π‘ Whatβs Next
More deep dives are coming soon on:
- Cloud Operations
- GenAI & Agentic AI
- DevOps Automation
- Data & Platform Engineering
Follow along for weekly insights and hands-on guides.
π Portfolio & Work
You can explore my full body of work, certifications, architecture projects, and technical articles here:
π Visit My Website
π οΈ Services I Offer
If you're looking for hands-on guidance or collaboration, I provide:
- Cloud Architecture Consulting (AWS / Azure)
- DevSecOps & Automation Design
- FinOps Optimization Reviews
- Technical Writing (Cloud, DevOps, GenAI)
- Product & Architecture Reviews
- Mentorship & 1:1 Technical Guidance
π€ Letβs Connect
Iβd love to hear your thoughts. Feel free to drop a comment or connect with me on:
π LinkedIn
For collaborations, consulting, or technical discussions, reach out at:
Found this helpful? Share it with your team.
β Star the repo β’ π Follow the series β’ π¬ Ask questions
Made by Sarvar Nadaf
π https://sarvarnadaf.com











Top comments (0)