terraform-infrastructure-as-code

Terraform Infrastructure as Code - Comprehensive Guide

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "terraform-infrastructure-as-code" with this command: npx skills add manutej/luxor-claude-marketplace/manutej-luxor-claude-marketplace-terraform-infrastructure-as-code

Terraform Infrastructure as Code - Comprehensive Guide

Table of Contents

  • Introduction to Terraform

  • Core Concepts

  • Resources

  • Data Sources

  • Variables and Outputs

  • Modules

  • State Management

  • Workspaces

  • Provider Configuration

  • Advanced Features

  • Dependencies

  • Provisioners

  • Best Practices

  • CI/CD Integration

Introduction to Terraform

Terraform is an open-source Infrastructure as Code (IaC) tool created by HashiCorp that enables you to define and provision infrastructure using a declarative configuration language called HashiCorp Configuration Language (HCL). Terraform manages external resources such as public cloud infrastructure, private cloud infrastructure, network appliances, and software as a service.

Key Benefits

  • Declarative Configuration: Define what your infrastructure should look like, not how to create it

  • Cloud-Agnostic: Works with multiple cloud providers and services

  • Version Control: Infrastructure code can be versioned and reviewed

  • Plan Before Apply: Preview changes before applying them

  • Resource Graph: Automatically manages dependencies between resources

  • State Management: Tracks the current state of your infrastructure

Terraform Workflow

Initialize Terraform working directory

terraform init

Initialize and upgrade provider versions

terraform init -upgrade

Initialize with backend configuration

terraform init -backend-config="bucket=my-state-bucket"

Generate a plan

terraform plan

Save plan to file for later apply

terraform plan -out=tfplan

Plan with specific variable values

terraform plan -var="region=us-west-2" -var="instance_type=t2.micro"

Apply with interactive approval

terraform apply

Auto-approve without confirmation

terraform apply -auto-approve

Apply a saved plan file

terraform apply tfplan

Destroy with confirmation prompt

terraform destroy

Auto-approve destruction

terraform destroy -auto-approve

Core Concepts

  1. Resources

Resources are the most fundamental elements in Terraform. They represent infrastructure objects like virtual machines, networks, databases, or DNS records.

Basic resource declaration

resource "aws_instance" "web" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro"

tags = { Name = "WebServer" Environment = "production" } }

  1. Providers

Providers are plugins that allow Terraform to interact with cloud platforms, SaaS providers, and other APIs.

AWS provider configuration

provider "aws" { region = "us-west-2"

default_tags { tags = { ManagedBy = "Terraform" Project = "MyApp" } } }

  1. State

Terraform stores information about your infrastructure in a state file. This state is used to map real-world resources to your configuration and track metadata.

  1. Configuration Language (HCL)

HCL is designed to be both human-readable and machine-friendly, making it ideal for infrastructure configuration.

Resources

Resources are the building blocks of Terraform configurations. Each resource block describes one or more infrastructure objects.

Basic Resource Syntax

resource "resource_type" "resource_name" { argument1 = "value1" argument2 = "value2"

nested_block { nested_argument = "nested_value" } }

Resource Examples

AWS EC2 Instance

resource "aws_instance" "app_server" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro" key_name = "my-keypair"

vpc_security_group_ids = [aws_security_group.app.id] subnet_id = aws_subnet.public.id

user_data = <<-EOF #!/bin/bash echo "Hello, World!" > index.html nohup busybox httpd -f -p 8080 & EOF

tags = { Name = "AppServer" Environment = "production" ManagedBy = "Terraform" } }

AWS VPC

resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" enable_dns_hostnames = true enable_dns_support = true

tags = { Name = "main-vpc" } }

resource "aws_subnet" "public" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" availability_zone = "us-west-2a" map_public_ip_on_launch = true

tags = { Name = "public-subnet" } }

resource "aws_subnet" "private" { vpc_id = aws_vpc.main.id cidr_block = "10.0.2.0/24" availability_zone = "us-west-2a"

tags = { Name = "private-subnet" } }

AWS S3 Bucket

resource "aws_s3_bucket" "data" { bucket = "my-app-data-bucket-12345"

tags = { Name = "Data Bucket" Environment = "production" } }

resource "aws_s3_bucket_versioning" "data" { bucket = aws_s3_bucket.data.id

versioning_configuration { status = "Enabled" } }

resource "aws_s3_bucket_server_side_encryption_configuration" "data" { bucket = aws_s3_bucket.data.id

rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } }

AWS Security Group

resource "aws_security_group" "web" { name = "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 { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] }

tags = { Name = "web-security-group" } }

Resource Lifecycle

resource "aws_instance" "example" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro"

lifecycle { create_before_destroy = true prevent_destroy = true ignore_changes = [tags] } }

Lifecycle options:

  • create_before_destroy : Create new resource before destroying the old one

  • prevent_destroy : Prevent accidental destruction of resources

  • ignore_changes : Ignore changes to specified attributes

  • replace_triggered_by : Force replacement when specific resources change

resource "aws_instance" "example" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro"

lifecycle { replace_triggered_by = [ aws_iam_policy.example.id ] } }

Data Sources

Data sources allow Terraform to use information defined outside of Terraform, or defined by another separate Terraform configuration.

Data Source Syntax

data "resource_type" "name" {

Query parameters

}

Data Source Examples

Query existing AWS resources

data "aws_ami" "ubuntu" { most_recent = true owners = ["099720109477"] # Canonical

filter { name = "name" values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"] } }

Use data source in resource

resource "aws_instance" "web" { ami = data.aws_ami.ubuntu.id instance_type = "t2.micro" }

Query availability zones

data "aws_availability_zones" "available" { state = "available" }

Query VPC

data "aws_vpc" "default" { default = true }

Query remote state

data "terraform_remote_state" "network" { backend = "s3"

config = { bucket = "terraform-state" key = "network/terraform.tfstate" region = "us-west-2" } }

Use remote state outputs

resource "aws_instance" "app" { ami = data.aws_ami.ubuntu.id instance_type = "t2.micro" subnet_id = data.terraform_remote_state.network.outputs.subnet_id

availability_zone = data.aws_availability_zones.available.names[0] }

Common Data Sources

AWS Account ID

data "aws_caller_identity" "current" {}

output "account_id" { value = data.aws_caller_identity.current.account_id }

AWS Region

data "aws_region" "current" {}

output "region" { value = data.aws_region.current.name }

Route53 Zone

data "aws_route53_zone" "primary" { name = "example.com" }

IAM Policy Document

data "aws_iam_policy_document" "assume_role" { statement { actions = ["sts:AssumeRole"]

principals {
  type        = "Service"
  identifiers = ["ec2.amazonaws.com"]
}

} }

Variables and Outputs

Input Variables

Variables allow you to parameterize your configurations for reusability.

Simple variable

variable "region" { type = string default = "us-west-2" description = "AWS region for resources" }

Variable with validation

variable "instance_type" { type = string default = "t2.micro"

validation { condition = contains(["t2.micro", "t2.small", "t2.medium"], var.instance_type) error_message = "Instance type must be t2.micro, t2.small, or t2.medium." } }

Complex type variable

variable "vpc_config" { type = object({ cidr_block = string azs = list(string) private_subnets = list(string) public_subnets = list(string) })

default = { cidr_block = "10.0.0.0/16" azs = ["us-west-2a", "us-west-2b"] private_subnets = ["10.0.1.0/24", "10.0.2.0/24"] public_subnets = ["10.0.101.0/24", "10.0.102.0/24"] } }

Variable Types

String variable

variable "environment" { type = string default = "development" }

Number variable

variable "instance_count" { type = number default = 3 }

Boolean variable

variable "enable_monitoring" { type = bool default = true }

List variable

variable "availability_zones" { type = list(string) default = ["us-west-2a", "us-west-2b", "us-west-2c"] }

Map variable

variable "ami_ids" { type = map(string) default = { us-west-2 = "ami-0c55b159cbfafe1f0" us-east-1 = "ami-0b69ea66ff7391e80" } }

Object variable

variable "database_config" { type = object({ engine = string engine_version = string instance_class = string allocated_storage = number })

default = { engine = "postgres" engine_version = "13.7" instance_class = "db.t3.micro" allocated_storage = 20 } }

Setting Variables

Command line

terraform apply -var="region=us-east-1" -var="instance_count=5"

Variable files

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

Environment variables

export TF_VAR_region=us-east-1 terraform apply

terraform.tfvars:

region = "us-west-2" instance_count = 3 environment = "production"

Output Values

Outputs make information about your infrastructure available for other configurations or display to users.

Simple output

output "instance_ip" { value = aws_instance.web.public_ip description = "The public IP of the web server" }

Output with sensitive data

output "database_password" { value = aws_db_instance.main.password sensitive = true }

Complex output

output "instance_details" { value = { id = aws_instance.web.id public_ip = aws_instance.web.public_ip private_ip = aws_instance.web.private_ip arn = aws_instance.web.arn } description = "Complete instance information" }

Output with depends_on

output "vpc_ready" { value = "VPC and subnets are ready" depends_on = [ aws_vpc.main, aws_subnet.private, aws_subnet.public ] }

Using Outputs

Show all outputs

terraform output

Get specific output value

terraform output instance_ip

Output in JSON format

terraform output -json

Use output in scripts

INSTANCE_IP=$(terraform output -raw instance_ip) echo "Server IP: $INSTANCE_IP"

Local Values

Local values assign a name to an expression for reuse within a module.

locals { common_tags = { ManagedBy = "Terraform" Environment = var.environment Project = "MyApp" }

name_prefix = "${var.project_name}-${var.environment}"

availability_zones = slice(data.aws_availability_zones.available.names, 0, 3) }

resource "aws_instance" "web" { ami = var.ami_id instance_type = var.instance_type

tags = merge( local.common_tags, { Name = "${local.name_prefix}-web-server" } ) }

Modules

Modules are containers for multiple resources that are used together. They enable you to create reusable components.

Module Structure

modules/ └── vpc/ ├── main.tf ├── variables.tf ├── outputs.tf └── README.md

Creating a Module

modules/vpc/main.tf:

resource "aws_vpc" "main" { cidr_block = var.cidr_block enable_dns_hostnames = var.enable_dns_hostnames enable_dns_support = var.enable_dns_support

tags = merge( var.tags, { Name = var.name } ) }

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 = merge( var.tags, { Name = "${var.name}-public-${count.index + 1}" Type = "public" } ) }

resource "aws_subnet" "private" { count = length(var.private_subnet_cidrs)

vpc_id = aws_vpc.main.id cidr_block = var.private_subnet_cidrs[count.index] availability_zone = var.availability_zones[count.index]

tags = merge( var.tags, { Name = "${var.name}-private-${count.index + 1}" Type = "private" } ) }

resource "aws_internet_gateway" "main" { vpc_id = aws_vpc.main.id

tags = merge( var.tags, { Name = "${var.name}-igw" } ) }

modules/vpc/variables.tf:

variable "name" { description = "Name prefix for VPC resources" type = string }

variable "cidr_block" { description = "CIDR block for VPC" type = string }

variable "public_subnet_cidrs" { description = "CIDR blocks for public subnets" type = list(string) }

variable "private_subnet_cidrs" { description = "CIDR blocks for private subnets" type = list(string) }

variable "availability_zones" { description = "Availability zones for subnets" type = list(string) }

variable "enable_dns_hostnames" { description = "Enable DNS hostnames in VPC" type = bool default = true }

variable "enable_dns_support" { description = "Enable DNS support in VPC" type = bool default = true }

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

modules/vpc/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 "private_subnet_ids" { description = "IDs of private subnets" value = aws_subnet.private[*].id }

output "internet_gateway_id" { description = "ID of the internet gateway" value = aws_internet_gateway.main.id }

Using Modules

Using a module from local path

module "vpc" { source = "./modules/vpc"

cidr_block = "10.0.0.0/16" region = var.region

tags = { Environment = "production" ManagedBy = "terraform" } }

Using a module from Terraform Registry

module "s3_bucket" { source = "terraform-aws-modules/s3-bucket/aws" version = "3.15.0"

bucket = "my-application-bucket" acl = "private"

versioning = { enabled = true } }

Using module outputs

resource "aws_instance" "app" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro" subnet_id = module.vpc.private_subnet_ids[0]

tags = { Name = "App Server" } }

Module with count

module "web_servers" { count = 3 source = "./modules/web-server"

name = "web-${count.index}" subnet = module.vpc.public_subnet_ids[count.index] }

Module with for_each

module "environments" { for_each = toset(["dev", "staging", "prod"]) source = "./modules/environment"

env_name = each.key vpc_cidr = "10.${index(["dev", "staging", "prod"], each.key)}.0.0/16" }

Module Sources

Local path

module "vpc" { source = "./modules/vpc" }

Terraform Registry

module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "5.1.0" }

GitHub

module "vpc" { source = "github.com/terraform-aws-modules/terraform-aws-vpc" }

GitHub with specific branch

module "vpc" { source = "github.com/terraform-aws-modules/terraform-aws-vpc?ref=v5.1.0" }

Git

module "vpc" { source = "git::https://github.com/terraform-aws-modules/terraform-aws-vpc.git" }

S3 bucket

module "vpc" { source = "s3::https://s3.amazonaws.com/my-bucket/vpc-module.zip" }

Module Versioning

module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 5.0" # Any version 5.x }

module "s3" { source = "terraform-aws-modules/s3-bucket/aws" version = ">= 3.0, < 4.0" # Between 3.0 and 4.0 }

State Management

Terraform state is a critical component that maps your configuration to real-world resources.

Local State

By default, Terraform stores state locally in a file named terraform.tfstate .

terraform { backend "local" { path = "terraform.tfstate" } }

Remote State

Remote state enables team collaboration and provides better security and reliability.

S3 Backend

S3 backend configuration

terraform { backend "s3" { bucket = "my-terraform-state" key = "production/terraform.tfstate" region = "us-west-2" encrypt = true dynamodb_table = "terraform-locks" } }

Setup DynamoDB for state locking:

resource "aws_dynamodb_table" "terraform_locks" { name = "terraform-locks" billing_mode = "PAY_PER_REQUEST" hash_key = "LockID"

attribute { name = "LockID" type = "S" }

tags = { Name = "Terraform State Lock Table" } }

Azure Backend

terraform { backend "azurerm" { resource_group_name = "terraform-rg" storage_account_name = "tfstate" container_name = "tfstate" key = "prod.terraform.tfstate" } }

Terraform Cloud Backend

terraform { cloud { organization = "my-organization"

workspaces {
  name = "production"
}

} }

Consul Backend

terraform { backend "consul" { address = "consul.example.com:8500" scheme = "https" path = "terraform/production" } }

State Commands

List all resources in state

terraform state list

Show specific resource details

terraform state show aws_instance.example

Remove resource from state (doesn't destroy)

terraform state rm aws_instance.example

Move resource to new address

terraform state mv aws_instance.example aws_instance.web_server

Show current state

terraform show

Show specific plan file

terraform show tfplan

Output state in JSON format

terraform show -json > state.json

Pull remote state

terraform state pull > terraform.tfstate

Push local state to remote

terraform state push terraform.tfstate

State Locking

State locking prevents concurrent operations that could corrupt your state.

terraform { backend "s3" { bucket = "my-terraform-state" key = "terraform.tfstate" region = "us-west-2" dynamodb_table = "terraform-state-lock" encrypt = true } }

Importing Existing Resources

Import AWS instance

terraform import aws_instance.example i-0abc123def456

Import with module

terraform import module.network.aws_vpc.main vpc-0123456789abcdef

Configuration needed before import:

resource "aws_instance" "example" {

Configuration will be populated after import

Define the basic structure matching the resource

}

State Migration

Initialize with backend

terraform init

Migrate from local to remote

terraform init -migrate-state

Backend configuration from CLI

terraform init -backend-config="bucket=my-other-bucket"
-backend-config="key=my-state"

Workspaces

Workspaces allow you to manage multiple instances of a single configuration.

Workspace Commands

List workspaces

terraform workspace list

Create new workspace

terraform workspace new production

Switch to workspace

terraform workspace select staging

Show current workspace

terraform workspace show

Delete workspace

terraform workspace delete development

Workspace Workflow

Example workflow:

terraform workspace new development terraform apply # Creates resources in development workspace

terraform workspace new staging terraform apply # Creates separate resources in staging workspace

terraform workspace new production terraform apply # Creates separate resources in production workspace

Using Workspace in Configuration

locals { environment = terraform.workspace }

resource "aws_instance" "web" { ami = var.ami_id instance_type = terraform.workspace == "production" ? "t3.large" : "t3.micro"

tags = { Name = "web-${terraform.workspace}" Environment = terraform.workspace } }

Workspace-specific variables

variable "instance_counts" { type = map(number) default = { development = 1 staging = 2 production = 5 } }

resource "aws_instance" "app" { count = var.instance_counts[terraform.workspace]

ami = var.ami_id instance_type = "t3.micro"

tags = { Name = "app-${terraform.workspace}-${count.index + 1}" } }

Workspace State Isolation

Each workspace has its own state file:

  • terraform.tfstate.d/development/terraform.tfstate

  • terraform.tfstate.d/staging/terraform.tfstate

  • terraform.tfstate.d/production/terraform.tfstate

Provider Configuration

Providers are plugins that enable Terraform to interact with cloud platforms and other services.

Single Provider

terraform { required_version = ">= 1.0"

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

provider "aws" { region = var.region

default_tags { tags = { ManagedBy = "Terraform" Project = "MyApp" } } }

Multiple Provider Instances (Aliases)

provider "aws" { alias = "east" region = "us-east-1" }

provider "aws" { alias = "west" region = "us-west-2" }

resource "aws_instance" "east_server" { provider = aws.east

ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro" }

resource "aws_instance" "west_server" { provider = aws.west

ami = "ami-0123456789abcdef" instance_type = "t2.micro" }

Multi-Cloud Setup

terraform { required_providers { aws = { source = "hashicorp/aws" version = "> 5.0" } azurerm = { source = "hashicorp/azurerm" version = "> 3.0" } google = { source = "hashicorp/google" version = "~> 4.0" } } }

provider "aws" { region = "us-west-2" }

provider "azurerm" { features {} subscription_id = var.azure_subscription_id }

provider "google" { project = var.gcp_project_id region = "us-central1" }

AWS resources

resource "aws_s3_bucket" "data" { bucket = "my-data-bucket" }

Azure resources

resource "azurerm_storage_account" "data" { name = "mydatastorageacct" resource_group_name = azurerm_resource_group.main.name location = azurerm_resource_group.main.location account_tier = "Standard" account_replication_type = "LRS" }

GCP resources

resource "google_storage_bucket" "data" { name = "my-data-bucket-gcp" location = "US" }

Provider Configuration in Modules

Root module

provider "aws" { region = "us-west-2" }

provider "aws" { alias = "dr" region = "us-east-1" }

module "app" { source = "./modules/app"

providers = { aws = aws aws.dr = aws.dr } }

Module configuration:

modules/app/main.tf

terraform { required_providers { aws = { source = "hashicorp/aws" configuration_aliases = [aws.dr] } } }

resource "aws_instance" "primary" { provider = aws

...

}

resource "aws_instance" "dr" { provider = aws.dr

...

}

Advanced Features

Dynamic Blocks

Dynamic blocks allow you to dynamically construct repeatable nested blocks.

variable "ingress_rules" { type = list(object({ description = string from_port = number to_port = number protocol = string cidr_blocks = list(string) }))

default = [ { description = "HTTP" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] }, { description = "HTTPS" from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ] }

resource "aws_security_group" "web" { name = "web-sg" description = "Security group for web servers" vpc_id = aws_vpc.main.id

dynamic "ingress" { for_each = var.ingress_rules content { description = ingress.value.description from_port = ingress.value.from_port to_port = ingress.value.to_port protocol = ingress.value.protocol cidr_blocks = ingress.value.cidr_blocks } }

egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } }

For_Each Meta-Argument

resource "aws_s3_bucket" "buckets" { for_each = toset(["logs", "data", "backups"])

bucket = "my-app-${each.key}"

tags = { Purpose = each.key } }

With map

variable "instances" { type = map(object({ ami = string instance_type = string }))

default = { web = { ami = "ami-0c55b159cbfafe1f0" instance_type = "t3.micro" } app = { ami = "ami-0c55b159cbfafe1f0" instance_type = "t3.small" } } }

resource "aws_instance" "servers" { for_each = var.instances

ami = each.value.ami instance_type = each.value.instance_type

tags = { Name = each.key } }

Count Meta-Argument

resource "aws_instance" "servers" { count = 3

ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro"

tags = { Name = "server-${count.index}" } }

Conditional resource creation

variable "create_db" { type = bool default = true }

resource "aws_db_instance" "database" { count = var.create_db ? 1 : 0

engine = "postgres" instance_class = "db.t3.micro" allocated_storage = 20 }

Conditional Expressions

resource "aws_instance" "web" { ami = var.ami_id instance_type = var.environment == "production" ? "t3.large" : "t3.micro"

tags = { Name = var.environment == "production" ? "prod-web" : "dev-web" } }

locals { instance_count = var.environment == "production" ? 5 : var.environment == "staging" ? 2 : 1 }

Functions

locals {

String functions

name_upper = upper(var.name) name_lower = lower(var.name) name_title = title(var.name)

Collection functions

subnet_count = length(var.subnet_cidrs) first_az = element(var.availability_zones, 0) all_azs = join(",", var.availability_zones)

Numeric functions

min_instances = min(var.instance_count, 10) max_instances = max(var.instance_count, 1)

Type conversion

instance_count_string = tostring(var.instance_count) az_set = toset(var.availability_zones)

Map functions

merged_tags = merge(var.common_tags, var.specific_tags)

File functions

user_data = file("${path.module}/scripts/init.sh") config = templatefile("${path.module}/templates/config.tpl", { environment = var.environment region = var.region })

Encoding functions

encoded = base64encode("secret data") decoded = base64decode(var.encoded_data)

Date/Time functions

timestamp = timestamp()

IP Network functions

cidr_subnets = cidrsubnets("10.0.0.0/16", 8, 8, 8, 8) }

Terraform Functions in Practice

Creating multiple subnets across availability zones

locals { subnet_cidrs = cidrsubnets(var.vpc_cidr, 8, 8, 8, 8) }

data "aws_availability_zones" "available" { state = "available" }

resource "aws_subnet" "public" { count = 3

vpc_id = aws_vpc.main.id cidr_block = local.subnet_cidrs[count.index] availability_zone = data.aws_availability_zones.available.names[count.index]

tags = { Name = "public-${count.index + 1}" } }

Dependencies

Implicit Dependencies

Terraform automatically infers dependencies from resource references.

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

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

resource "aws_instance" "web" { ami = var.ami_id instance_type = "t2.micro" subnet_id = aws_subnet.public.id # Implicit dependency }

Explicit Dependencies

Use depends_on when dependencies cannot be inferred automatically.

resource "aws_instance" "app" { ami = "ami-0c55b159cbfafe1f0" instance_type = "t2.micro"

depends_on = [aws_db_instance.database] }

resource "aws_db_instance" "database" { engine = "postgres" instance_class = "db.t3.micro" allocated_storage = 20 }

Module Dependencies

module "vpc" { source = "./modules/vpc"

cidr_block = "10.0.0.0/16" }

module "app" { source = "./modules/app"

vpc_id = module.vpc.vpc_id subnet_id = module.vpc.private_subnet_ids[0]

depends_on = [module.vpc] }

Provisioners

Provisioners are used to execute scripts on a local or remote machine as part of resource creation or destruction.

Local-Exec Provisioner

resource "aws_instance" "web" { ami = var.ami_id instance_type = "t2.micro"

provisioner "local-exec" { command = "echo ${self.private_ip} >> private_ips.txt" }

provisioner "local-exec" { when = destroy command = "echo 'Instance ${self.id} destroyed' >> destroy_log.txt" } }

Remote-Exec Provisioner

resource "aws_instance" "web" { ami = var.ami_id instance_type = "t2.micro" key_name = var.key_name

connection { type = "ssh" user = "ubuntu" private_key = file(var.private_key_path) host = self.public_ip }

provisioner "remote-exec" { inline = [ "sudo apt-get update", "sudo apt-get install -y nginx", "sudo systemctl start nginx", "sudo systemctl enable nginx" ] } }

File Provisioner

resource "aws_instance" "web" { ami = var.ami_id instance_type = "t2.micro" key_name = var.key_name

connection { type = "ssh" user = "ubuntu" private_key = file(var.private_key_path) host = self.public_ip }

provisioner "file" { source = "scripts/init.sh" destination = "/tmp/init.sh" }

provisioner "remote-exec" { inline = [ "chmod +x /tmp/init.sh", "sudo /tmp/init.sh" ] } }

Provisioner Failure Behavior

resource "aws_instance" "web" { ami = var.ami_id instance_type = "t2.micro"

provisioner "local-exec" { command = "./configure.sh" on_failure = continue # or fail (default) } }

Best Practices

  1. State Management
  • Always use remote state for team collaboration

  • Enable state locking to prevent concurrent modifications

  • Enable encryption for sensitive data

  • Regular state backups for disaster recovery

  1. Code Organization

project/ ├── environments/ │ ├── dev/ │ │ ├── main.tf │ │ ├── variables.tf │ │ ├── outputs.tf │ │ └── terraform.tfvars │ ├── staging/ │ └── production/ ├── modules/ │ ├── vpc/ │ ├── compute/ │ └── database/ └── global/ └── s3/

  1. Variable Management

Use meaningful descriptions

variable "instance_type" { description = "EC2 instance type for web servers" type = string default = "t3.micro" }

Use validation

variable "environment" { description = "Deployment environment" type = string

validation { condition = contains(["dev", "staging", "production"], var.environment) error_message = "Environment must be dev, staging, or production." } }

  1. Naming Conventions

Resource naming

resource "aws_instance" "web_server" { # Use descriptive names tags = { Name = "${var.project}-${var.environment}-web-${count.index + 1}" } }

Module naming

module "vpc_production" { source = "./modules/vpc" }

  1. DRY Principle

Use locals for repeated values

locals { common_tags = { Project = var.project_name Environment = var.environment ManagedBy = "Terraform" CostCenter = var.cost_center } }

resource "aws_instance" "web" { tags = merge( local.common_tags, { Name = "web-server" Role = "webserver" } ) }

  1. Security Best Practices

Never hardcode credentials

BAD

provider "aws" { access_key = "AKIAIOSFODNN7EXAMPLE" secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" }

GOOD - Use environment variables or IAM roles

provider "aws" {

Credentials from environment or IAM role

}

Use sensitive flag for outputs

output "database_password" { value = aws_db_instance.main.password sensitive = true }

Encrypt sensitive data

resource "aws_s3_bucket" "data" { bucket = "my-data-bucket" }

resource "aws_s3_bucket_server_side_encryption_configuration" "data" { bucket = aws_s3_bucket.data.id

rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" } } }

  1. Testing

Validate syntax

terraform validate

Format code

terraform fmt -recursive

Plan before apply

terraform plan -out=tfplan

Review plan

terraform show tfplan

  1. Version Control

.gitignore:

Local .terraform directories

*/.terraform/

.tfstate files

*.tfstate .tfstate.

Crash log files

crash.log crash.*.log

Exclude variable files that may contain sensitive data

*.tfvars *.tfvars.json

Ignore override files

override.tf override.tf.json *_override.tf *_override.tf.json

CLI configuration files

.terraformrc terraform.rc

Plan files

*.tfplan

  1. Module Best Practices

Module versioning

module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 5.0" # Pin to major version }

Module documentation

Always include README.md with:

- Description

- Requirements

- Providers

- Inputs

- Outputs

- Example usage

  1. Resource Tagging

Consistent tagging strategy

locals { required_tags = { Project = var.project_name Environment = var.environment ManagedBy = "Terraform" Owner = var.owner_email CostCenter = var.cost_center Compliance = var.compliance_level } }

Use default tags at provider level

provider "aws" { region = var.region

default_tags { tags = local.required_tags } }

CI/CD Integration

GitHub Actions

.github/workflows/terraform.yml:

name: Terraform

on: push: branches: [main] pull_request: branches: [main]

env: TF_VERSION: 1.5.0

jobs: terraform: name: Terraform runs-on: ubuntu-latest

steps:
  - name: Checkout
    uses: actions/checkout@v3

  - name: Setup Terraform
    uses: hashicorp/setup-terraform@v2
    with:
      terraform_version: ${{ env.TF_VERSION }}

  - name: Terraform Format
    run: terraform fmt -check -recursive

  - name: Terraform Init
    run: terraform init
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

  - name: Terraform Validate
    run: terraform validate

  - name: Terraform Plan
    run: terraform plan -no-color
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

  - name: Terraform Apply
    if: github.ref == 'refs/heads/main' &#x26;&#x26; github.event_name == 'push'
    run: terraform apply -auto-approve
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

GitLab CI

.gitlab-ci.yml:

image: name: hashicorp/terraform:1.5.0 entrypoint: [""]

stages:

  • validate
  • plan
  • apply

before_script:

  • terraform init

validate: stage: validate script: - terraform validate - terraform fmt -check -recursive

plan: stage: plan script: - terraform plan -out=tfplan artifacts: paths: - tfplan expire_in: 1 week

apply: stage: apply script: - terraform apply -auto-approve tfplan dependencies: - plan only: - main when: manual

Jenkins Pipeline

Jenkinsfile:

pipeline { agent any

environment {
    TF_VERSION = '1.5.0'
    AWS_CREDENTIALS = credentials('aws-credentials')
}

stages {
    stage('Checkout') {
        steps {
            checkout scm
        }
    }

    stage('Terraform Init') {
        steps {
            sh 'terraform init'
        }
    }

    stage('Terraform Validate') {
        steps {
            sh 'terraform validate'
            sh 'terraform fmt -check -recursive'
        }
    }

    stage('Terraform Plan') {
        steps {
            sh 'terraform plan -out=tfplan'
        }
    }

    stage('Terraform Apply') {
        when {
            branch 'main'
        }
        steps {
            input message: 'Apply Terraform changes?', ok: 'Apply'
            sh 'terraform apply -auto-approve tfplan'
        }
    }
}

post {
    always {
        cleanWs()
    }
}

}

Azure DevOps

azure-pipelines.yml:

trigger:

  • main

pool: vmImage: 'ubuntu-latest'

variables:

  • name: terraformVersion value: '1.5.0'

stages:

  • stage: Validate jobs:

    • job: ValidateTerraform steps:
      • task: TerraformInstaller@0 inputs: terraformVersion: $(terraformVersion)

      • task: TerraformTaskV2@2 displayName: 'Terraform Init' inputs: provider: 'aws' command: 'init' backendServiceAWS: 'AWS-Connection'

      • task: TerraformTaskV2@2 displayName: 'Terraform Validate' inputs: provider: 'aws' command: 'validate'

      • script: terraform fmt -check -recursive displayName: 'Terraform Format Check'

  • stage: Plan dependsOn: Validate jobs:

    • job: PlanTerraform steps:
      • task: TerraformTaskV2@2 displayName: 'Terraform Plan' inputs: provider: 'aws' command: 'plan' commandOptions: '-out=tfplan'
  • stage: Apply dependsOn: Plan condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) jobs:

    • deployment: ApplyTerraform environment: 'production' strategy: runOnce: deploy: steps: - task: TerraformTaskV2@2 displayName: 'Terraform Apply' inputs: provider: 'aws' command: 'apply' commandOptions: '-auto-approve tfplan'

Pre-commit Hooks

.pre-commit-config.yaml:

repos:

Automated Testing

test/terraform_test.go:

package test

import ( "testing" "github.com/gruntwork-io/terratest/modules/terraform" "github.com/stretchr/testify/assert" )

func TestTerraformVPC(t *testing.T) { terraformOptions := &terraform.Options{ TerraformDir: "../examples/vpc",

    Vars: map[string]interface{}{
        "cidr_block": "10.0.0.0/16",
        "region":     "us-west-2",
    },
}

defer terraform.Destroy(t, terraformOptions)

terraform.InitAndApply(t, terraformOptions)

vpcId := terraform.Output(t, terraformOptions, "vpc_id")
assert.NotEmpty(t, vpcId)

}

Additional Commands and Tools

Terraform Console

Start console

terraform console

Example session:

aws_instance.example.public_ip "54.183.22.100" var.region "us-west-2" length(aws_instance.example.tags) 3

Terraform Graph

Generate graph in DOT format

terraform graph > graph.dot

Convert to image with Graphviz

terraform graph | dot -Tpng > graph.png

Generate graph for specific plan

terraform graph -type=plan > plan-graph.dot

Terraform Taint

Taint a resource (mark for recreation)

terraform taint aws_instance.example

Untaint a resource

terraform untaint aws_instance.example

Terraform Providers

List required providers

terraform providers

Show provider schemas

terraform providers schema -json > schemas.json

Lock provider versions

terraform providers lock

Conclusion

This comprehensive guide covers the essential aspects of Terraform Infrastructure as Code, from basic concepts to advanced patterns and CI/CD integration. By following these practices and patterns, you can build maintainable, scalable, and secure infrastructure deployments across any cloud provider.

Key takeaways:

  • Use modules for reusable components

  • Implement remote state with locking for team collaboration

  • Leverage workspaces for environment isolation

  • Follow security best practices and never hardcode credentials

  • Integrate Terraform into CI/CD pipelines for automated deployments

  • Use validation, formatting, and testing tools

  • Maintain clear documentation and consistent naming conventions

  • Implement proper tagging strategies for resource management

Terraform enables infrastructure teams to adopt DevOps practices, improve collaboration, reduce manual errors, and accelerate deployment cycles while maintaining full control and visibility over infrastructure changes.

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

golang-backend-development

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

fastapi-microservices-development

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

spring-boot-development

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

expressjs-development

No summary provided by upstream source.

Repository SourceNeeds Review