Terraform Core Concepts

Understanding Terraform's core concepts is essential for working effectively with Infrastructure as Code. This guide covers the fundamental building blocks of Terraform.

Configuration Files

What are Configuration Files?

Terraform uses text files with the .tf extension to describe infrastructure. These files use HCL (HashiCorp Configuration Language) syntax.

File Types:

  • *.tf - Configuration files (main.tf, variables.tf, outputs.tf)
  • *.tfvars - Variable values (terraform.tfvars, prod.tfvars)
  • *.tfstate - State files (terraform.tfstate)
  • .terraform.lock.hcl - Dependency lock file

Providers

What are Providers?

Providers are plugins that Terraform uses to interact with cloud providers, SaaS providers, and other APIs. Each provider adds a set of resource types and data sources that Terraform can manage.

Common Providers:

  • AWS - hashicorp/aws
  • Azure - hashicorp/azurerm
  • GCP - hashicorp/google
  • Docker - kreuzwerker/docker
  • Kubernetes - hashicorp/kubernetes

Provider Block Example:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "us-east-1"
  
  # Optional: Configure via environment variables
  # AWS_ACCESS_KEY_ID
  # AWS_SECRET_ACCESS_KEY
  # AWS_REGION
}

Resources

What are Resources?

Resources are the most important element in Terraform. Each resource block describes one or more infrastructure objects, such as virtual networks, compute instances, or higher-level components.

Resource Block Syntax:

resource "resource_type" "resource_name" {
  # Resource configuration
  argument1 = value1
  argument2 = value2
  
  # Nested blocks
  nested_block {
    nested_argument = value
  }
}

Example: AWS EC2 Instance

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"
  
  tags = {
    Name = "WebServer"
    Environment = "Production"
  }
}

Accessing Resource Attributes:

# Reference resource
resource "aws_instance" "web" {
  # ...
}

# Use resource attributes in other resources
resource "aws_eip" "web_ip" {
  instance = aws_instance.web.id
  vpc      = true
}

# Output resource attributes
output "instance_id" {
  value = aws_instance.web.id
}

output "public_ip" {
  value = aws_instance.web.public_ip
}

Data Sources

What are Data Sources?

Data sources allow Terraform to use information defined outside of Terraform, or defined by another separate Terraform configuration. Data sources fetch data that can be used elsewhere in your configuration.

Data Source Example:

# Fetch latest AMI
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-gp2"]
  }
}

# Use data source in resource
resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t2.micro"
}

Common Data Sources:

  • aws_ami - Get AMI information
  • aws_vpc - Get VPC information
  • aws_subnet - Get subnet information
  • aws_availability_zones - Get availability zones

Variables

Input Variables

Variables allow you to customize aspects of Terraform modules without altering the module's source code. They make your configuration more flexible and reusable.

Variable Declaration (variables.tf):

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
  
  validation {
    condition = can(regex("^t[23]\\.", var.instance_type))
    error_message = "Instance type must be t2 or t3 family."
  }
}

variable "tags" {
  description = "Tags to apply to resources"
  type        = map(string)
  default     = {}
}

variable "enabled" {
  description = "Enable or disable resources"
  type        = bool
  default     = true
}

variable "subnet_ids" {
  description = "List of subnet IDs"
  type        = list(string)
}

Variable Types:

  • string - Text values
  • number - Numeric values
  • bool - True/false values
  • list(type) - Ordered list
  • map(type) - Key-value pairs
  • set(type) - Unordered collection
  • object({...}) - Structured objects
  • tuple([...]) - Fixed-length list

Using Variables:

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = var.instance_type  # Reference variable
  
  tags = var.tags
}

Providing Variable Values:

# terraform.tfvars
instance_type = "t3.medium"
tags = {
  Environment = "production"
  Project     = "web-app"
}

# Command line
terraform apply -var="instance_type=t3.large"

# Environment variables
export TF_VAR_instance_type="t3.large"
terraform apply

Outputs

Output Values

Output values make information about your infrastructure available on the command line, and can expose information for other Terraform configurations to use.

Output Declaration (outputs.tf):

output "instance_id" {
  description = "ID of the EC2 instance"
  value       = aws_instance.web.id
  sensitive   = false
}

output "public_ip" {
  description = "Public IP address of the instance"
  value       = aws_instance.web.public_ip
}

output "private_key" {
  description = "Private key (sensitive)"
  value       = tls_private_key.example.private_key_pem
  sensitive   = true
}

output "all_tags" {
  description = "All tags from the instance"
  value       = aws_instance.web.tags
}

Using Outputs:

# View all outputs
terraform output

# View specific output
terraform output instance_id

# View outputs as JSON
terraform output -json

# Use output in other configurations
instance_id = module.ec2.instance_id

Local Values

What are Local Values?

Local values assign a name to an expression so you can use it multiple times within a module without repeating it. They help reduce duplication and make your code more readable.

Local Values Example:

locals {
  common_tags = {
    Environment = var.environment
    Project     = "web-app"
    ManagedBy   = "Terraform"
  }
  
  instance_name = "${var.environment}-${var.project}-instance"
  
  # Computed values
  availability_zones = data.aws_availability_zones.available.names
}

# Use local values
resource "aws_instance" "web" {
  tags = local.common_tags
  
  tags = merge(local.common_tags, {
    Name = local.instance_name
  })
}

Expressions

Types of Expressions

Interpolation:

resource "aws_instance" "web" {
  ami = "ami-${var.ami_id}"
  
  user_data = <<-EOF
    echo "Instance ID: ${aws_instance.web.id}"
  EOF
}

Functions:

# String functions
resource "aws_s3_bucket" "example" {
  bucket = "${var.project_name}-${random_id.bucket_suffix.hex}"
}

# Numeric functions
resource "aws_instance" "web" {
  count = length(var.subnet_ids)
  # ...
}

# Collection functions
locals {
  merged_tags = merge(var.common_tags, var.additional_tags)
  
  subnet_cidrs = [for s in var.subnets : s.cidr]
  
  # Conditional
  instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"
}

Built-in Functions:

  • upper(), lower() - String manipulation
  • length() - Get length of collections
  • merge() - Merge maps
  • split(), join() - String operations
  • file() - Read file contents
  • jsonencode(), jsondecode() - JSON operations

Modules

What are Modules?

Modules are containers for multiple resources that are used together. They allow you to organize your code, reuse infrastructure, and create abstractions.

Using Modules:

module "vpc" {
  source = "./modules/vpc"
  
  vpc_cidr = "10.0.0.0/16"
  environment = var.environment
  
  tags = var.tags
}

# Access module outputs
resource "aws_instance" "web" {
  subnet_id = module.vpc.private_subnet_id
  # ...
}
💡 Tip: Modules can be local directories, Git repositories, or Terraform Registry modules.

State

What is State?

Terraform must store state about your managed infrastructure and configuration. This state is used to map real world resources to your configuration, keep track of metadata, and improve performance.

State File Location:

  • Local - terraform.tfstate (default)
  • Remote - S3, Azure Storage, GCS, Terraform Cloud
💡 Best Practice: Use remote state backends in production for team collaboration and state locking.

Dependencies

Resource Dependencies

Terraform automatically tracks dependencies between resources. When you reference one resource from another, Terraform understands the dependency relationship.

Implicit Dependencies:

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "web" {
  vpc_id     = aws_vpc.main.id  # Implicit dependency
  cidr_block = "10.0.0.0/24"
}

Explicit Dependencies:

resource "aws_instance" "web" {
  # ...
  
  depends_on = [
    aws_iam_role.example,
    aws_security_group.example
  ]
}

Next Steps