Tutorial 6: Define Input Variables
Learning Objectives
- Understand the purpose and benefits of input variables
- Learn different variable types and their usage
- Practice defining and using variables in Terraform configurations
- Implement variable validation and best practices
Why Use Variables?
Problems with Hardcoded Values
# Hardcoded configuration - NOT recommended
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1d0" # Hardcoded
instance_type = "t2.micro" # Hardcoded
tags = {
Name = "web-server-production" # Hardcoded
}
}
Issues:
- Configuration not reusable across environments
- Hard to maintain and update
- No flexibility for different use cases
- Risk of errors when copying configurations
Benefits of Variables
- Reusability: Same configuration for multiple environments
- Flexibility: Easy to customize deployments
- Maintainability: Single place to change values
- Validation: Ensure inputs meet requirements
- Documentation: Self-documenting configurations
Variable Declaration Syntax
Basic Variable Block
variable "variable_name" {
description = "A description of the variable"
type = string
default = "default_value"
}
Variable Components
- Name: Identifier for the variable
- Description: Human-readable explanation
- Type: Data type constraint
- Default: Optional default value
- Validation: Optional validation rules
- Sensitive: Mark as sensitive for security
Variable Types
String Variables
variable "region" {
description = "AWS region for resources"
type = string
default = "us-west-2"
}
variable "environment" {
description = "Environment name"
type = string
# No default - must be provided
}
Number Variables
variable "instance_count" {
description = "Number of instances to create"
type = number
default = 1
}
variable "disk_size" {
description = "Disk size in GB"
type = number
default = 20
}
Boolean Variables
variable "enable_monitoring" {
description = "Enable CloudWatch monitoring"
type = bool
default = true
}
variable "create_load_balancer" {
description = "Whether to create a load balancer"
type = bool
default = false
}
List Variables
variable "availability_zones" {
description = "List of availability zones"
type = list(string)
default = ["us-west-2a", "us-west-2b"]
}
variable "allowed_ports" {
description = "List of allowed ports"
type = list(number)
default = [80, 443, 22]
}
Map Variables
variable "instance_types" {
description = "Instance types by environment"
type = map(string)
default = {
dev = "t2.micro"
prod = "t3.medium"
}
}
variable "common_tags" {
description = "Common tags for all resources"
type = map(string)
default = {
Project = "terraform-tutorial"
ManagedBy = "Terraform"
}
}
Object Variables
variable "vpc_config" {
description = "VPC configuration object"
type = object({
cidr_block = string
enable_dns_hostnames = bool
enable_dns_support = bool
})
default = {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
}
}
variable "database_config" {
description = "Database configuration"
type = object({
engine = string
engine_version = string
instance_class = string
allocated_storage = number
})
}
Using Variables in Configuration
Variable Reference Syntax
# Reference a variable using var.variable_name
resource "aws_instance" "web" {
ami = var.ami_id
instance_type = var.instance_type
count = var.instance_count
tags = var.common_tags
}
Complete Example with Variables
variables.tf
variable "region" {
description = "AWS region"
type = string
default = "us-west-2"
}
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "project_name" {
description = "Name of the project"
type = string
default = "terraform-tutorial"
}
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}
variable "create_load_balancer" {
description = "Whether to create a load balancer"
type = bool
default = false
}
variable "allowed_cidr_blocks" {
description = "CIDR blocks allowed to access the instance"
type = list(string)
default = ["0.0.0.0/0"]
}
variable "tags" {
description = "Additional tags"
type = map(string)
default = {}
}
main.tf (using variables)
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.region
}
# Local values combining variables
locals {
common_tags = merge(
{
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
},
var.tags
)
name_prefix = "${var.project_name}-${var.environment}"
}
# Data source using variable
data "aws_ami" "amazon_linux" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-gp2"]
}
}
# VPC using variables
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-vpc"
})
}
# Security group using variables
resource "aws_security_group" "web" {
name = "${local.name_prefix}-web-sg"
description = "Security group for ${var.environment} web servers"
vpc_id = aws_vpc.main.id
dynamic "ingress" {
for_each = [80, 443, 22]
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = var.allowed_cidr_blocks
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-web-sg"
})
}
# EC2 instance using variables
resource "aws_instance" "web" {
ami = data.aws_ami.amazon_linux.id
instance_type = var.instance_type
tags = merge(local.common_tags, {
Name = "${local.name_prefix}-web-server"
})
}
Variable Validation
Basic Validation
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
validation {
condition = contains([
"t2.micro", "t2.small", "t2.medium",
"t3.micro", "t3.small", "t3.medium"
], var.instance_type)
error_message = "Instance type must be a valid t2 or t3 type."
}
}
Advanced Validation
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
validation {
condition = can(cidrhost(var.vpc_cidr, 0))
error_message = "VPC CIDR must be a valid IPv4 CIDR block."
}
validation {
condition = split("/", var.vpc_cidr)[1] <= 24
error_message = "VPC CIDR block must be /24 or larger."
}
}
variable "environment" {
description = "Environment name"
type = string
validation {
condition = can(regex("^(dev|staging|prod)$", var.environment))
error_message = "Environment must be dev, staging, or prod."
}
}
variable "tags" {
description = "Resource tags"
type = map(string)
validation {
condition = contains(keys(var.tags), "Environment")
error_message = "Tags must include an Environment key."
}
}
Sensitive Variables
Marking Variables as Sensitive
variable "database_password" {
description = "Password for the database"
type = string
sensitive = true
}
variable "api_key" {
description = "API key for external service"
type = string
sensitive = true
}
Using Sensitive Variables
resource "aws_db_instance" "main" {
# ... other configuration
password = var.database_password # Will be hidden in logs
}
Providing Variable Values
Method 1: terraform.tfvars File
# terraform.tfvars
region = "us-east-1"
environment = "prod"
instance_type = "t3.medium"
create_load_balancer = true
tags = {
Owner = "DevOps Team"
Purpose = "Web Application"
}
Method 2: Environment-Specific Files
# dev.tfvars
environment = "dev"
instance_type = "t2.micro"
create_load_balancer = false
# prod.tfvars
environment = "prod"
instance_type = "t3.medium"
create_load_balancer = true
Usage:
terraform apply -var-file="dev.tfvars"
terraform apply -var-file="prod.tfvars"
Method 3: Command Line
terraform apply -var="environment=dev" -var="instance_type=t2.micro"
Method 4: Environment Variables
export TF_VAR_environment="dev"
export TF_VAR_instance_type="t2.micro"
terraform apply
Variable Precedence (Highest to Lowest)
- Command line
-var
flags *.auto.tfvars
files (alphabetical order)terraform.tfvars
file*.auto.tfvars.json
files (alphabetical order)terraform.tfvars.json
file- Environment variables (
TF_VAR_name
) - Variable defaults
Complex Variable Examples
Nested Object Variable
variable "web_server_config" {
description = "Web server configuration"
type = object({
instance_type = string
count = number
monitoring = object({
enabled = bool
detailed_monitoring = bool
})
storage = object({
size = number
type = string
encrypted = bool
})
})
default = {
instance_type = "t2.micro"
count = 1
monitoring = {
enabled = true
detailed_monitoring = false
}
storage = {
size = 20
type = "gp3"
encrypted = true
}
}
}
Using Complex Variables
resource "aws_instance" "web" {
count = var.web_server_config.count
ami = data.aws_ami.amazon_linux.id
instance_type = var.web_server_config.instance_type
monitoring = var.web_server_config.monitoring.enabled
root_block_device {
volume_size = var.web_server_config.storage.size
volume_type = var.web_server_config.storage.type
encrypted = var.web_server_config.storage.encrypted
}
}
Variable with Dynamic Types
variable "instance_configs" {
description = "Map of instance configurations"
type = map(object({
instance_type = string
ami_id = string
subnet_id = string
}))
default = {
web = {
instance_type = "t2.micro"
ami_id = "ami-0c55b159cbfafe1d0"
subnet_id = "subnet-12345"
}
db = {
instance_type = "t3.medium"
ami_id = "ami-0c55b159cbfafe1d0"
subnet_id = "subnet-67890"
}
}
}
Local Values vs Variables
When to Use Local Values
# variables.tf
variable "environment" {
type = string
}
variable "project_name" {
type = string
}
# main.tf
locals {
# Computed values
name_prefix = "${var.project_name}-${var.environment}"
# Complex expressions
is_production = var.environment == "prod"
# Merged configurations
common_tags = {
Environment = var.environment
Project = var.project_name
ManagedBy = "Terraform"
CreatedAt = timestamp()
}
}
Best Practices
1. Organization
# Recommended file structure
āāā variables.tf # All variable declarations
āāā main.tf # Main resources
āāā outputs.tf # Output declarations
āāā terraform.tfvars # Default values
āāā dev.tfvars # Environment-specific values
āāā prod.tfvars # Environment-specific values
2. Naming Conventions
# Good variable names
variable "vpc_cidr_block" { } # Clear and specific
variable "enable_monitoring" { } # Boolean prefix
variable "instance_count" { } # Descriptive
# Avoid
variable "cidr" { } # Too generic
variable "monitoring" { } # Unclear type
variable "count" { } # Conflicts with built-in
3. Documentation
variable "instance_type" {
description = <<-EOF
EC2 instance type for web servers.
Recommended: t2.micro for dev, t3.medium for prod.
See: https://aws.amazon.com/ec2/instance-types/
EOF
type = string
default = "t2.micro"
}
4. Validation Best Practices
variable "environment" {
description = "Environment name"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = <<-EOF
Environment must be one of: dev, staging, prod.
Current value: ${var.environment}
EOF
}
}
5. Default Values
# Provide sensible defaults for development
variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro" # Safe, cheap default
}
# No default for required environment-specific values
variable "database_password" {
description = "Database password"
type = string
sensitive = true
# No default - force explicit setting
}
Testing Variable Configurations
Validate Configuration
terraform validate
Test with Different Values
# Test development configuration
terraform plan -var-file="dev.tfvars"
# Test production configuration
terraform plan -var-file="prod.tfvars"
# Test edge cases
terraform plan -var="instance_count=0"
Variable Testing Script
#!/bin/bash
# test-variables.sh
echo "Testing variable configurations..."
# Test development
echo "Testing development configuration..."
terraform plan -var-file="dev.tfvars" -out=dev.plan
# Test production
echo "Testing production configuration..."
terraform plan -var-file="prod.tfvars" -out=prod.plan
# Test with minimum values
echo "Testing minimum configuration..."
terraform plan \
-var="environment=dev" \
-var="instance_count=0" \
-out=min.plan
echo "All tests completed. Review plan files."
Key Takeaways
- Variables make configurations reusable and flexible
- Use appropriate variable types for data validation
- Implement validation rules to catch errors early
- Mark sensitive variables appropriately
- Use multiple methods to provide variable values
- Follow naming conventions and organization best practices
- Document variables thoroughly with descriptions
- Test configurations with different variable values
Next Steps
- Complete Tutorial 7: Query Data Sources
- Learn about output values and their usage
- Explore local values for computed expressions
- Practice with complex variable structures