DEV Community

Saurabh Ahuja
Saurabh Ahuja

Posted on

Terraform Basics

Table of Contents

  1. What is Terraform?
  2. Installation
  3. Core Concepts
  4. Basic Examples
  5. State Management
  6. Variables and Outputs
  7. Modules
  8. Common Commands
  9. Best Practices

What is Terraform?

Terraform is an Infrastructure as Code (IaC) tool developed by HashiCorp. It allows you to define and provision infrastructure using a declarative configuration language called HCL (HashiCorp Configuration Language).

Key Benefits (with details and limitations):

  • Declarative: Describe what you want, not how to get it
    • Better than imperative (using python/java/go sdk's). Terraform figures out how to achieve the state, simply declare, I want se bucket with versioning enabled. Complexity of calling the API is hidden.
  • Multi-cloud: Works with AWS, Azure, GCP, and many other providers
    • Terraform's architecture is built on a lightweight core binary that leverages a powerful plugin-based system. Providers—which interface with cloud platforms and services—are downloaded automatically during initialization (terraform init), enabling support for major public clouds and hundreds of other infrastructure providers without requiring a large installation footprint.
  • Version Control: Infrastructure code can be versioned like application code
    • Terraform configuration files serve as the single source of truth for infrastructure. Since all changes are made through code, manual configuration drift is eliminated, and we can track every infrastructure change through version control systems like Git, enabling full audit trails and rollback capabilities.
  • Idempotent: Running the same configuration multiple times produces the same result
    • While idempotency is a key advantage, some cloud providers impose limitations that can affect this behavior. For example, both AWS and Google Cloud Platform (GCP) have mandatory deletion waiting periods for KMS keys. AWS KMS keys enter a scheduled deletion state and cannot be fully removed for 7-30 days (configurable), while GCP Cloud KMS key versions remain in a "scheduled for destruction" state for typically 30 days. This means that attempting to destroy and immediately recreate a KMS key with the same name will fail, as the key still exists in a pending deletion state. Such provider-specific constraints can prevent true idempotency in certain scenarios, requiring careful resource naming strategies or waiting periods between destroy and recreate operations.
  • State Management: Tracks the current state of your infrastructure
    • The state file is critical for Terraform to understand and manage your infrastructure. However, migrating state files during major Terraform version upgrades or backend changes can be complex and may require manual intervention. While Terraform provides migration tools (like terraform init -migrate-state), careful planning and testing are essential to ensure state compatibility and avoid accidental infrastructure destruction during the migration process.

Installation

macOS

brew install terraform
Enter fullscreen mode Exit fullscreen mode

Linux

wget https://releases.hashicorp.com/terraform/1.6.0/terraform_1.6.0_linux_amd64.zip
unzip terraform_1.6.0_linux_amd64.zip
sudo mv terraform /usr/local/bin/
Enter fullscreen mode Exit fullscreen mode

Verify Installation

terraform version
Enter fullscreen mode Exit fullscreen mode

Core Concepts

1. Providers

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

2. Resources

Resources are the most important element in Terraform. They represent infrastructure objects like virtual machines, networks, databases, etc.

3. Data Sources

Data sources allow you to fetch and use information from existing infrastructure or external systems. Unlike resources, data sources are read-only and don't create, modify, or destroy anything. They're useful for referencing existing resources, getting the latest AMI IDs, querying VPC information, or retrieving data from external APIs.

4. Variables

Variables allow you to parameterize your configurations.

5. Outputs

Outputs expose information about your infrastructure after it's created.

6. State

Terraform stores the state of your infrastructure in a state file, which tracks what resources exist and their current configuration.


Basic Examples

Prerequisites

Before running the examples below, ensure you have:

  1. Terraform binary - Install Terraform and specify the version in your configuration
  2. Providers - Declare required providers with version constraints in your Terraform configuration
  3. Cloud credentials - Configure authentication for cloud providers (AWS, GCP, Azure, etc.)

Example 1: Local File Resource (doesn't require credentials)

Create a file called main.tf:

terraform {
  required_version = ">= 1.0"
}

# Create a local file
resource "local_file" "example" {
  filename = "example.txt"
  content  = "Hello, Terraform!"
}
Enter fullscreen mode Exit fullscreen mode

Commands:

terraform init      # Initialize Terraform
terraform plan      # Preview changes
terraform apply     # Apply changes
terraform destroy   # Remove resources
Enter fullscreen mode Exit fullscreen mode

Example 2: AWS S3 Bucket

terraform {
  required_version = ">= 1.0"

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

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

# Create an S3 bucket
resource "aws_s3_bucket" "example" {
  bucket = "my-terraform-example-bucket-2024"

  tags = {
    Name        = "Example Bucket"
    Environment = "Development"
  }
}

# Enable versioning
resource "aws_s3_bucket_versioning" "example" {
  bucket = aws_s3_bucket.example.id

  versioning_configuration {
    status = "Enabled"
  }
}
Enter fullscreen mode Exit fullscreen mode

Setup:

# Configure AWS credentials
aws configure

# Initialize and apply
terraform init
terraform plan
terraform apply
Enter fullscreen mode Exit fullscreen mode

Example 3: Google Cloud Storage (GCS) Bucket

terraform {
  required_version = ">= 1.0"

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

provider "google" {
  project = "your-project-id"  # Replace with your GCP project ID
  region  = "us-central1" # specify region
}

# Create a GCS bucket
resource "google_storage_bucket" "example" {
  name          = "my-terraform-example-bucket-2025"  # Must be globally unique
  location      = "US"
  force_destroy = false  # Set to true to allow deletion of non-empty bucket

  # Enable versioning
  versioning {
    enabled = true
  }

  # Lifecycle rules
  lifecycle_rule {
    condition {
      age = 30
    }
    action {
      type = "Delete"
    }
  }

  # Uniform bucket-level access
  uniform_bucket_level_access = true

  # Labels
  labels = {
    environment = "development"
    managed-by  = "terraform"
  }
}

# Set bucket IAM policy (optional)
resource "google_storage_bucket_iam_member" "public_read" {
  bucket = google_storage_bucket.example.name
  role   = "roles/storage.objectViewer"
  member = "allUsers"
}

# Output the bucket URL
output "bucket_url" {
  value       = google_storage_bucket.example.url
  description = "URL of the GCS bucket"
}

output "bucket_name" {
  value       = google_storage_bucket.example.name
  description = "Name of the GCS bucket"
}
Enter fullscreen mode Exit fullscreen mode

Setup:

# Authenticate with Google Cloud
gcloud auth application-default login

# Set your project
gcloud config set project your-project-id

# Initialize and apply
terraform init
terraform plan
terraform apply
Enter fullscreen mode Exit fullscreen mode

State Management

Local State (Default)

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

Remote State (Recommended for Teams)

Store state in a remote backend to enable team collaboration and state locking. Examples for AWS S3 and Google Cloud Storage (GCS):

AWS S3 Backend:

terraform {
  backend "s3" {
    bucket = "my-terraform-state-bucket"
    key    = "terraform.tfstate"
    region = "us-east-1"

    # Enable state locking with DynamoDB
    dynamodb_table = "terraform-state-lock"
    encrypt        = true
  }
}
Enter fullscreen mode Exit fullscreen mode

Google Cloud Storage (GCS) Backend:

terraform {
  backend "gcs" {
    bucket = "my-terraform-state-bucket"
    prefix = "terraform/state"

    # Optional: Enable state locking (requires Cloud Storage bucket with versioning)
    # State locking is automatically enabled with GCS backend
  }
}
Enter fullscreen mode Exit fullscreen mode

State Commands

terraform state list              # List all resources in state
terraform state show <resource>   # Show details of a resource
terraform state mv <old> <new>    # Move a resource in state
terraform state rm <resource>     # Remove a resource from state
Enter fullscreen mode Exit fullscreen mode

Variables and Outputs

Variables Example

Create variables.tf:

variable "region" {
  description = "AWS region"
  type        = string
  default     = "us-east-1"
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
}

variable "environment" {
  description = "Environment name"
  type        = string
  validation {
    condition     = contains(["dev", "staging", "prod"], var.environment)
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "tags" {
  description = "Common tags for all resources"
  type        = map(string)
  default = {
    Project = "Terraform-101"
    ManagedBy = "Terraform"
  }
}
Enter fullscreen mode Exit fullscreen mode

Create terraform.tfvars:

region        = "us-west-2"
instance_type = "t3.small"
environment   = "dev"

tags = {
  Project     = "Terraform-101"
  ManagedBy   = "Terraform"
  Owner       = "DevOps Team"
}
Enter fullscreen mode Exit fullscreen mode

Update main.tf to use variables:

provider "aws" {
  region = var.region
}

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

  tags = merge(
    var.tags,
    {
      Name        = "example-instance-${var.environment}"
      Environment = var.environment
    }
  )
}
Enter fullscreen mode Exit fullscreen mode

Outputs Example

Create outputs.tf:

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

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

output "instance_arn" {
  description = "ARN of the EC2 instance"
  value       = aws_instance.example.arn
  sensitive   = false
}
Enter fullscreen mode Exit fullscreen mode

Modules

Modules are reusable Terraform configurations. They help organize and encapsulate infrastructure.

Using a Module

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

  vpc_cidr = "10.0.0.0/16"
  environment = "production"

  tags = {
    Project = "Terraform-101"
  }
}

# Reference module outputs
resource "aws_instance" "web" {
  subnet_id = module.vpc.public_subnet_id
  # ...
}
Enter fullscreen mode Exit fullscreen mode

Creating a Module

Create modules/vpc/main.tf:

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

variable "environment" {
  description = "Environment name"
  type        = string
}

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

resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr
  tags = merge(
    var.tags,
    {
      Name        = "vpc-${var.environment}"
      Environment = var.environment
    }
  )
}

output "vpc_id" {
  value = aws_vpc.main.id
}

output "vpc_cidr" {
  value = aws_vpc.main.cidr_block
}
Enter fullscreen mode Exit fullscreen mode

Using Public Modules

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["us-east-1a", "us-east-1b", "us-east-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  enable_nat_gateway = true
  enable_vpn_gateway = false

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}
Enter fullscreen mode Exit fullscreen mode

Common Commands

Basic Workflow

# Initialize Terraform
terraform init

# Format code
terraform fmt

# Validate configuration
terraform validate

# Plan changes
terraform plan

# Apply changes
terraform apply

# Apply with auto-approve (use with caution)
terraform apply -auto-approve

# Destroy infrastructure
terraform destroy

# Show current state
terraform show
Enter fullscreen mode Exit fullscreen mode

Best Practices

1. File Organization

project/
├── main.tf          # Main resources
├── variables.tf     # Variable declarations
├── outputs.tf      # Output definitions
├── terraform.tfvars # Variable values
├── providers.tf    # Provider configuration
└── modules/        # Reusable modules
    └── vpc/
        ├── main.tf
        ├── variables.tf
        └── outputs.tf
Enter fullscreen mode Exit fullscreen mode

2. Use Version Constraints

terraform {
  required_version = ">= 1.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"  # Allows 5.x but not 6.0
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Use Variables, Not Hardcoded Values

# ❌ Bad
resource "aws_instance" "example" {
  instance_type = "t2.micro"
}

# ✅ Good
resource "aws_instance" "example" {
  instance_type = var.instance_type
}
Enter fullscreen mode Exit fullscreen mode

4. Use Meaningful Resource Names

# ❌ Bad
resource "aws_instance" "a" { }

# ✅ Good
resource "aws_instance" "web_server" { }
Enter fullscreen mode Exit fullscreen mode

5. Tag Your Resources

resource "aws_instance" "example" {
  tags = {
    Name        = "web-server"
    Environment = var.environment
    Project     = "Terraform-101"
    ManagedBy   = "Terraform"
  }
}
Enter fullscreen mode Exit fullscreen mode

6. Use Remote State

Store state in a remote backend (S3, Azure Storage, GCS) for team collaboration.

7. Use Workspaces for Environments

terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
Enter fullscreen mode Exit fullscreen mode

8. Validate and Format Before Committing

terraform fmt -recursive
terraform validate
Enter fullscreen mode Exit fullscreen mode

9. Use Data Sources

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

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

resource "aws_instance" "example" {
  ami = data.aws_ami.amazon_linux.id
  # ...
}
Enter fullscreen mode Exit fullscreen mode

10. Use terraform.tfvars for Sensitive Data

Never commit terraform.tfvars with secrets. Use environment variables or secret management:

export TF_VAR_db_password="secret-password"
Enter fullscreen mode Exit fullscreen mode

Common Patterns

Conditional Resources

resource "aws_instance" "example" {
  count = var.create_instance ? 1 : 0

  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = var.instance_type
}
Enter fullscreen mode Exit fullscreen mode

For Each Loop

variable "regions" {
  type = map(object({
    cidr = string
  }))
  default = {
    us-east-1 = { cidr = "10.0.0.0/16" }
    us-west-2 = { cidr = "10.1.0.0/16" }
  }
}

resource "aws_vpc" "example" {
  for_each = var.regions

  cidr_block = each.value.cidr

  tags = {
    Name = "vpc-${each.key}"
  }
}
Enter fullscreen mode Exit fullscreen mode

Locals for Computed Values

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

  instance_name = "${var.project_name}-${var.environment}-${var.instance_type}"
}

resource "aws_instance" "example" {
  tags = local.common_tags
  # ...
}
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

Common Issues

  1. State Lock Error
   terraform force-unlock <LOCK_ID>
Enter fullscreen mode Exit fullscreen mode
  1. Provider Not Found
   terraform init -upgrade
Enter fullscreen mode Exit fullscreen mode
  1. State Out of Sync
   terraform refresh
   terraform plan
Enter fullscreen mode Exit fullscreen mode
  1. Import Existing Resources
   terraform import aws_s3_bucket.example my-bucket-name
Enter fullscreen mode Exit fullscreen mode

Next Steps

  1. Practice: Start with local resources, then move to cloud providers
  2. Learn Modules: Create reusable modules for common patterns
  3. State Management: Set up remote state backends
  4. CI/CD Integration: Integrate Terraform into your CI/CD pipeline
  5. Advanced Topics: Learn about Terraform Cloud, opa policies, and workspaces

Resources


Happy Terraforming! 🚀

Top comments (0)