Day 4: Terraform Basics - Variables & Outputs

I'm a cloud-native enthusiast and tech blogger, sharing insights on Kubernetes, AWS, CI/CD, and Linux across my blog and Facebook page. Passionate about modern infrastructure and microservices, I aim to help others understand and leverage cloud-native technologies for scalable, efficient solutions.
Welcome to Day 4! Today weβll learn how to make our Terraform configurations flexible and reusable using variables and outputs. No more hardcoded valuesβweβre writing professional, maintainable code!
π― Todayβs Goals
Understand input variables and why they matter
Learn variable types and validation
Master output values
Create reusable, parameterized configurations
Build a complete infrastructure with variables
π The Problem with Hardcoded Values
Remember our VPC from yesterday?
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16" # β Hardcoded
tags = {
Name = "main-vpc" # β Hardcoded
}
}
Problems:
Canβt reuse for different environments (dev, staging, prod)
Changing values requires editing code
Difficult to customize deployments
Not DRY (Donβt Repeat Yourself)
π₯ Input Variables
Input variables are parameters for your Terraform configuration. Think of them like function arguments in programming.
Basic Variable Syntax
variable "variable_name" {
description = "Description of the variable"
type = string
default = "default_value"
}
Variable Structure Explained
ββββββββββββββββββββββββββββββββββββββββ
β variable "vpc_cidr" { β
β βββββ¬ββββ β
β β β
β Variable name β
β β
β description = "VPC CIDR block" β
β βββββββ¬ββββββ βββββββ¬ββββββββ β
β β β β
β Attribute Value β
β β
β type = string β
β default = "10.0.0.0/16" β
β } β
ββββββββββββββββββββββββββββββββββββββββ
Using Variables
Reference variables with var. prefix:
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = var.vpc_name
}
}
π Variable Types
Terraform supports several variable types:
1. String
variable "region" {
type = string
default = "us-east-1"
}
2. Number
variable "instance_count" {
type = number
default = 2
}
3. Bool
variable "enable_dns" {
type = bool
default = true
}
4. List
variable "availability_zones" {
type = list(string)
default = ["us-east-1a", "us-east-1b"]
}
# Access: var.availability_zones[0]
5. Map
variable "instance_types" {
type = map(string)
default = {
dev = "t2.micro"
prod = "t3.large"
}
}
# Access: var.instance_types["dev"]
6. Object
variable "vpc_config" {
type = object({
cidr_block = string
name = string
enable_dns = bool
})
default = {
cidr_block = "10.0.0.0/16"
name = "main-vpc"
enable_dns = true
}
}
# Access: var.vpc_config.cidr_block
π― Variable Definition Precedence
Terraform loads variables in this order (later sources override earlier):
1. Environment variables (TF_VAR_name)
2. terraform.tfvars (auto-loaded)
3. *.auto.tfvars (auto-loaded alphabetically)
4. -var flag (command line)
5. -var-file flag (command line)
Example:
# Environment variable
export TF_VAR_region="us-west-2"
# terraform.tfvars file
region = "us-east-1"
# Command line (highest priority)
terraform apply -var="region=eu-west-1"
# Result: region = "eu-west-1"
π Ways to Set Variable Values
Method 1: Default Values (in variables.tf)
variable "region" {
type = string
default = "us-east-1"
}
Method 2: terraform.tfvars File
# terraform.tfvars
region = "us-east-1"
instance_type = "t2.micro"
instance_count = 3
Method 3: Custom .tfvars File
# production.tfvars
region = "us-east-1"
instance_type = "t3.large"
instance_count = 10
Apply with:
terraform apply -var-file="production.tfvars"
Method 4: Environment Variables
export TF_VAR_region="us-east-1"
export TF_VAR_instance_type="t2.micro"
terraform apply
Method 5: Command Line
terraform apply -var="region=us-east-1" -var="instance_type=t2.micro"
Method 6: Interactive Prompt
If no value is provided and thereβs no default:
$ terraform apply
var.region Enter a value: us-east-1
β Variable Validation
Add validation rules to ensure correct values:
variable "region" {
type = string
description = "AWS region"
validation {
condition = can(regex("^(us|eu|ap)-", var.region))
error_message = "Region must start with us-, eu-, or ap-."
}
}
variable "instance_count" {
type = number
description = "Number of instances"
validation {
condition = var.instance_count > 0 && var.instance_count <= 10
error_message = "Instance count must be between 1 and 10."
}
}
π€ Output Values
Outputs expose information about your infrastructure after itβs created.
Basic Output Syntax
output "output_name" {
description = "Description of the output"
value = resource.name.attribute
}
Example Outputs
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "IDs of public subnets"
value = aws_subnet.public[*].id
}
output "instance_public_ips" {
description = "Public IPs of instances"
value = aws_instance.web[*].public_ip
}
Sensitive Outputs
Mark sensitive data to hide from console output:
output "database_password" {
description = "Database password"
value = aws_db_instance.main.password
sensitive = true
}
Using Outputs
View outputs after applying:
# View output
terraform output
# View specific output
terraform output vpc_id
# Output as JSON
terraform output -json
π§ͺ Hands-On Lab: Parameterized VPC Infrastructure
Letβs rebuild our VPC with variables and outputs!
Step 1: Create Project Structure
mkdir terraform-variables-lab
cd terraform-variables-lab
Create these files:
variables.tf- Variable definitionsmain.tf- Resourcesoutputs.tf- Output valuesterraform.tfvars- Variable values
Step 2: Create variables.tf
# variables.tf
variable "aws_region" {
description = "AWS region for resources"
type = string
default = "us-east-1"
}
variable "project_name" {
description = "Project name for tagging"
type = string
default = "terraform-learning"
}
variable "environment" {
description = "Environment (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.0.0.0/16"
}
variable "public_subnet_cidrs" {
description = "CIDR blocks for public subnets"
type = list(string)
default = ["10.0.1.0/24", "10.0.2.0/24"]
}
variable "availability_zones" {
description = "Availability zones"
type = list(string)
default = ["us-east-1a", "us-east-1b"]
}
variable "enable_dns_hostnames" {
description = "Enable DNS hostnames in VPC"
type = bool
default = true
}
variable "common_tags" {
description = "Common tags for all resources"
type = map(string)
default = {
ManagedBy = "Terraform"
}
}
Step 3: Create main.tf
# main.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = var.common_tags
}
}
# VPC
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = true
tags = {
Name = "${var.project_name}-${var.environment}-vpc"
Environment = var.environment
}
}
# Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-${var.environment}-igw"
Environment = var.environment
}
}
# Public Subnets
resource "aws_subnet" "public" {
count = length(var.public_subnet_cidrs)
vpc_id = aws_vpc.main.id
cidr_block = var.public_subnet_cidrs[count.index]
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-${var.environment}-public-subnet-${count.index + 1}"
Environment = var.environment
Type = "Public"
}
}
# Route Table
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.project_name}-${var.environment}-public-rt"
Environment = var.environment
}
}
# Route Table Associations
resource "aws_route_table_association" "public" {
count = length(var.public_subnet_cidrs)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
# Security Group
resource "aws_security_group" "web" {
name = "${var.project_name}-${var.environment}-web-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTP from anywhere"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS from anywhere"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "All traffic outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-${var.environment}-web-sg"
Environment = var.environment
}
}
Step 4: Create outputs.tf
# outputs.tf
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "vpc_cidr" {
description = "CIDR block of the VPC"
value = aws_vpc.main.cidr_block
}
output "public_subnet_ids" {
description = "IDs of public subnets"
value = aws_subnet.public[*].id
}
output "public_subnet_cidrs" {
description = "CIDR blocks of public subnets"
value = aws_subnet.public[*].cidr_block
}
output "internet_gateway_id" {
description = "ID of the Internet Gateway"
value = aws_internet_gateway.main.id
}
output "web_security_group_id" {
description = "ID of the web security group"
value = aws_security_group.web.id
}
output "availability_zones" {
description = "Availability zones used"
value = aws_subnet.public[*].availability_zone
}
output "environment" {
description = "Environment name"
value = var.environment
}
Step 5: Create terraform.tfvars
# terraform.tfvars
environment = "dev"
project_name = "myapp"
vpc_cidr = "10.0.0.0/16"
public_subnet_cidrs = ["10.0.1.0/24", "10.0.2.0/24"]
availability_zones = ["us-east-1a", "us-east-1b"]
enable_dns_hostnames = true
common_tags = {
ManagedBy = "Terraform"
Owner = "DevOps Team"
Purpose = "Learning"
}
Step 6: Initialize and Validate
terraform init
terraform fmt
terraform validate
Step 7: Plan with Different Environments
Development:
terraform plan
Staging (create staging.tfvars):
# staging.tfvars
environment = "staging"
vpc_cidr = "10.1.0.0/16"
public_subnet_cidrs = ["10.1.1.0/24", "10.1.2.0/24"]
terraform plan -var-file="staging.tfvars"
Step 8: Apply Configuration
terraform apply
Type yes when prompted.
Step 9: View Outputs
# All outputs
terraform output
# Specific output
terraform output vpc_id
# JSON format
terraform output -json > infrastructure.json
Expected output:
availability_zones = [
"us-east-1a",
"us-east-1b",
]
environment = "dev"
internet_gateway_id = "igw-xxxxx"
public_subnet_cidrs = [
"10.0.1.0/24",
"10.0.2.0/24",
]
public_subnet_ids = [
"subnet-xxxxx",
"subnet-yyyyy",
]
vpc_cidr = "10.0.0.0/16"
vpc_id = "vpc-xxxxx"
web_security_group_id = "sg-xxxxx"
Step 10: Test Variable Validation
Try an invalid environment:
terraform apply -var="environment=production"
Youβll get an error:
Error: Invalid value for variable
Environment must be dev, staging, or prod.
Step 11: Clean Up
terraform destroy
π Variable Best Practices
β DO:
Use descriptive names
variable "vpc_cidr_block" {} # β Clear variable "cidr" {} # β VagueAlways add descriptions
variable "region" { description = "AWS region for all resources" type = string }Specify types explicitly
variable "count" { type = number # β Explicit }Use validation when appropriate
validation { condition = var.instance_count > 0 error_message = "Must be positive." }Group-related variables
# Network variables variable "vpc_cidr" {} variable "subnet_cidrs" {} # Compute variables variable "instance_type" {} variable "instance_count" {}
β DONβT:
Donβt hardcode sensitive values
Donβt use overly complex variable structures
Donβt skip documentation
Donβt use inconsistent naming
π Summary
Today you learned:
β Input variables and their importance
β All variable types (string, number, bool, list, map, object)
β Variable definition precedence
β Multiple ways to set variable values
β Variable validation
β Output values and their uses
β Built parameterized, reusable infrastructure
π Tomorrowβs Preview
Day 5: Resource Dependencies & Data Sources
Tomorrow weβll:
Understand implicit vs explicit dependencies
Learn about
depends_onMaster data sources to query existing infrastructure
Reference external resources
Build complex interdependent infrastructure
π Challenge Exercise
Create a production.tfvars file that:
Uses a different VPC CIDR (
172.16.0.0/16)Creates 3 subnets instead of 2
Sets environment to βprodβ
Adds a custom tag βCostCenter = Productionβ
Then apply it:
terraform apply -var-file="production.tfvars"
Happy Learning! π
Thanks For Reading, Follow Me For More and subscribe youtube channel for the recap videos
Have a great day!..
β Day 3: Understanding Providers | Day 5: Dependencies & Data Sources β
Remember: Variables make your Terraform code reusable, flexible, and maintainable!



