Mastering Terraform Variables and Outputs
Published on: November 2, 2023 | Author: DevOps Engineering Team
Welcome to Part 5 of our Terraform Mastery Series! So far, you've learned HCL syntax and state management. Now it's time to make your configurations dynamic and reusable. Variables and outputs are the key to creating flexible, maintainable Terraform code that works across different environments and use cases.
What You'll Learn
Why Variables Matter
Variables transform static configurations into dynamic, reusable templates. Here's what they enable:
Environment Flexibility
Use the same configuration for dev, staging, and production with different variable values.
Team Collaboration
Different team members can use the same code with their own settings.
Security
Keep sensitive values out of your codebase and inject them at runtime.
Variables vs. Hardcoded Values
Hardcoded (inflexible): instance_type = "t3.micro"
Variable (flexible): instance_type = var.instance_size
Variables make your code adaptable to different requirements without changing the core logic.
Variable Types and Validation
Terraform supports several variable types with built-in validation:
Basic Variable Types
variable "instance_type" {
type = string
description = "EC2 instance type"
default = "t3.micro"
}
variable "instance_count" {
type = number
description = "Number of instances to create"
default = 1
}
variable "enable_monitoring" {
type = bool
description = "Enable detailed monitoring"
default = false
}
Complex Variable Types
variable "availability_zones" {
type = list(string)
description = "List of availability zones"
default = ["us-east-1a", "us-east-1b"]
}
variable "tags" {
type = map(string)
description = "Resource tags"
default = {
Environment = "dev"
Project = "terraform-learning"
}
}
variable "network_config" {
type = object({
vpc_cidr = string
subnet_count = number
enable_nat = bool
})
default = {
vpc_cidr = "10.0.0.0/16"
subnet_count = 2
enable_nat = true
}
}
Variable Validation
variable "environment" {
type = string
description = "Deployment environment"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "instance_size" {
type = string
description = "EC2 instance size"
validation {
condition = can(regex("^[tcm][23a]?\\.[a-z]+$", var.instance_size))
error_message = "Instance size must be a valid AWS instance type."
}
}
Sensitive Variables
variable "database_password" {
type = string
description = "Database administrator password"
sensitive = true
}
variable "api_keys" {
type = map(string)
description = "API keys for external services"
sensitive = true
}
Sensitive Variable Behavior
Terraform will redact sensitive variable values from console output and logs, but they are still stored in state files in plain text. Always encrypt your state files!
Variable Input Methods
Terraform provides multiple ways to set variable values, each with different use cases:
1. terraform.tfvars Files
Recommended for most projects. Create environment-specific files:
instance_type = "t3.large"
instance_count = 3
environment = "prod"
tags = {
Environment = "production"
CostCenter = "infrastructure"
}
Usage: terraform apply (automatically loaded)
2. Command Line (-var)
Quick overrides for individual variables:
terraform apply -var="instance_type=t3.large" \
-var="instance_count=5"
Best for: Quick testing and debugging
3. Environment Variables (TF_VAR_)
Great for CI/CD pipelines and automation:
export TF_VAR_instance_type="t3.large"
export TF_VAR_database_password="secret123"
terraform apply
Best for: Automated deployments and secrets
4. Variable Definition Files (.auto.tfvars)
Automatically loaded files for different environments:
environment = "prod"
instance_count = 5
enable_monitoring = true
Usage: terraform apply (automatically loaded)
Variable Precedence Rules
When the same variable is set multiple ways, Terraform follows specific precedence rules:
Precedence Example
If you have default = "t3.micro" but set TF_VAR_instance_type=t3.large, Terraform will use t3.large because environment variables have highest precedence.
Working with Sensitive Variables
Sensitive variables require special handling to protect your secrets:
Mark Variables as Sensitive
variable "db_password" {
type = string
sensitive = true
description = "Database password"
}
Use Environment Variables for Secrets
export TF_VAR_db_password="my-secret-password"
terraform apply
This keeps secrets out of your code and version control.
Never Commit Secrets
Add sensitive files to .gitignore:
# Terraform sensitive files
*.tfvars
*.tfstate
*.tfstate.backup
.terraform/
Output Values Deep Dive
Outputs expose information from your infrastructure for other configurations or users:
Basic Outputs
output "instance_ip" {
value = aws_instance.web.public_ip
description = "Public IP of the web server"
}
output "load_balancer_dns" {
value = aws_lb.web.dns_name
description = "DNS name of the load balancer"
}
Complex Outputs
output "all_instance_ips" {
value = aws_instance.web.*.public_ip
description = "List of all instance IPs"
}
output "security_group_rules" {
value = {
for sg in aws_security_group.web :
sg.name => sg.ingress[*].from_port
}
description = "Security group ingress rules"
}
Sensitive Outputs
output "database_password" {
value = aws_db_instance.main.password
sensitive = true
description = "Database administrator password"
}
Output Security Note
Even with sensitive = true, outputs are stored in plain text in state files. Always encrypt your state backend!
Real-World Examples
Let's see variables and outputs in action with a complete example:
variable "environment" {
type = string
description = "Deployment environment"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "instance_config" {
type = map(object({
instance_type = string
instance_count = number
}))
default = {
dev = {
instance_type = "t3.micro"
instance_count = 1
}
staging = {
instance_type = "t3.medium"
instance_count = 2
}
prod = {
instance_type = "t3.large"
instance_count = 3
}
}
}
resource "aws_instance" "web" {
count = var.instance_config[var.environment].instance_count
ami = "ami-0c02fb55956c7d316"
instance_type = var.instance_config[var.environment].instance_type
tags = {
Name = "web-server-${count.index}"
Environment = var.environment
}
}
output "web_instance_ips" {
value = aws_instance.web.*.public_ip
}
output "environment_summary" {
value = "Deployed ${var.instance_config[var.environment].instance_count} instances of type ${var.instance_config[var.environment].instance_type} in ${var.environment} environment"
}
Variables and Outputs Best Practices
Follow these guidelines for maintainable and secure configurations:
Use Descriptive Names
Choose clear, meaningful names for variables and outputs:
# Good
variable "database_instance_type" {}
# Avoid
variable "db_type" {}
Provide Defaults When Possible
Default values make configurations easier to use:
variable "instance_type" {
type = string
default = "t3.micro"
}
Add Descriptions
Document the purpose and expected values:
variable "environment" {
description = "Deployment environment (dev, staging, prod)"
type = string
}
Use Validation
Catch errors early with input validation:
variable "port" {
type = number
validation {
condition = var.port > 0 && var.port < 65536
error_message = "Port must be between 1 and 65535"
}
}
Key Takeaways
- Variables make configurations reusable across environments
- Use appropriate types and validation for safety
- Follow precedence rules for variable assignment
- Mark sensitive data to protect secrets
- Outputs expose important information for other systems
- Never commit secrets to version control
In our next tutorial, we'll explore Terraform Modules, where you'll learn how to create reusable, composable infrastructure components that can be shared across your organization.
No comments:
Post a Comment