Post

Terraform Intermediate: Variables, Outputs, and Data Sources

Master Terraform variables, outputs, data sources, and learn how to structure your configurations effectively

Terraform Intermediate Concepts

After understanding the basics, it’s time to make your Terraform configurations more flexible and maintainable. This post covers essential intermediate concepts that will help you write better infrastructure code.

Variables and Inputs

Variable Types and Declarations

Variables make your configurations flexible and reusable. Here are different ways to declare variables:

# Basic variable declaration variable "environment" { type = string description = "Environment name (dev, staging, prod)" default = "dev" } # List variable variable "availability_zones" { type = list(string) description = "List of availability zones" } # Map variable variable "instance_tags" { type = map(string) default = { Environment = "dev" Team = "devops" } } # Object variable variable "vpc_config" { type = object({ cidr_block = string name = string enable_dns = bool }) }

Using Variables

resource "aws_vpc" "main" { cidr_block = var.vpc_config.cidr_block tags = merge(var.instance_tags, { Name = var.vpc_config.name }) }

Working with tfvars

Create different configurations for different environments using .tfvars files:

# prod.tfvars environment = "prod" vpc_config = { cidr_block = "10.0.0.0/16" name = "prod-vpc" enable_dns = true } availability_zones = ["us-west-2a", "us-west-2b", "us-west-2c"]

Apply with:

terraform apply -var-file="prod.tfvars"

Outputs

Outputs let you expose specific values that can be queried or used by other Terraform configurations:

output "vpc_id" { description = "The ID of the VPC" value = aws_vpc.main.id } output "subnet_ids" { description = "List of subnet IDs" value = [for subnet in aws_subnet.main : subnet.id] } # Sensitive output output "database_password" { description = "Database password" value = aws_db_instance.main.password sensitive = true }

Data Sources

Data sources let you query existing resources:

# Find the latest Amazon Linux 2 AMI data "aws_ami" "amazon_linux_2" { most_recent = true owners = ["amazon"] filter { name = "name" values = ["amzn2-ami-hvm-*-x86_64-gp2"] } } # Use the AMI in an instance resource "aws_instance" "web" { ami = data.aws_ami.amazon_linux_2.id instance_type = "t2.micro" } # Get all availability zones data "aws_availability_zones" "available" { state = "available" }

Local Values

Use locals to avoid repetition and compute values once:

locals { common_tags = { Environment = var.environment Project = var.project_name Terraform = "true" } subnet_count = length(data.aws_availability_zones.available.names) } resource "aws_subnet" "main" { count = local.subnet_count vpc_id = aws_vpc.main.id cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index) availability_zone = data.aws_availability_zones.available.names[count.index] tags = merge(local.common_tags, { Name = "subnet-${count.index + 1}" }) }

File Structure Best Practices

Organize your Terraform code with this recommended structure:

project/ ├── main.tf # Main configuration file ├── variables.tf # Variable declarations ├── outputs.tf # Output declarations ├── providers.tf # Provider configurations ├── terraform.tfvars # Variable values └── environments/ # Environment-specific configurations ├── dev/ │ └── terraform.tfvars └── prod/ └── terraform.tfvars

Next Steps

In our next post, we’ll explore advanced concepts including:

  • Terraform workspaces
  • Remote state management
  • Modules
  • Complex expressions and functions

Remember to always:

  • Use meaningful variable and resource names
  • Document your variables and outputs
  • Keep your configurations DRY (Don’t Repeat Yourself)
  • Use version control
  • Test your configurations before applying to production
This post is licensed under CC BY 4.0 by the author.