Skip to main content

Command Palette

Search for a command to run...

Day 4: Terraform Basics - Variables & Outputs

Updated
β€’9 min read
Day 4: Terraform Basics - Variables & Outputs
S

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 definitions

  • main.tf - Resources

  • outputs.tf - Output values

  • terraform.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:

  1. Use descriptive names

     variable "vpc_cidr_block" {}  # βœ… Clear
     variable "cidr" {}             # ❌ Vague
    
  2. Always add descriptions

     variable "region" {
       description = "AWS region for all resources"
       type        = string
     }
    
  3. Specify types explicitly

     variable "count" {
       type = number  # βœ… Explicit
     }
    
  4. Use validation when appropriate

     validation {
       condition     = var.instance_count > 0
       error_message = "Must be positive."
     }
    
  5. Group-related variables

     # Network variables
     variable "vpc_cidr" {}
     variable "subnet_cidrs" {}
    
     # Compute variables
     variable "instance_type" {}
     variable "instance_count" {}
    

❌ DON’T:

  1. Don’t hardcode sensitive values

  2. Don’t use overly complex variable structures

  3. Don’t skip documentation

  4. 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_on

  • Master data sources to query existing infrastructure

  • Reference external resources

  • Build complex interdependent infrastructure

πŸ’­ Challenge Exercise

Create a production.tfvars file that:

  1. Uses a different VPC CIDR (172.16.0.0/16)

  2. Creates 3 subnets instead of 2

  3. Sets environment to β€œprod”

  4. 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!

T

Thank you!

More from this blog

S

StackOps - Diary

33 posts

Welcome to the StackOps - Diary. We’re dedicated to empowering the tech community. We delve into cloud-native and microservices technologies, sharing knowledge to build modern, scalable solutions.