SynfraCore
Synfracore
Start Learning
Navigation

Academies

Platform

RoadmapsLabsCertificationsInterviewPYQsAI AssistantCareer
Start Learning Free🗺️ Learning Roadmaps

TerraformIntermediate

Real-world patterns, best practices, and deeper topics

✍️
Written by senior engineers. Reviewed for technical accuracy.· Updated 2025 · SynfraCore Terraform Team
Expert Content

Terraform — Intermediate

Modules

hcl
# modules/vpc/main.tf
variable "cidr"        { type = string }
variable "environment" { type = string }
variable "azs"         { type = list(string) }

locals {
  public_cidrs  = [for i, az in var.azs : cidrsubnet(var.cidr, 8, i)]
  private_cidrs = [for i, az in var.azs : cidrsubnet(var.cidr, 8, i + 10)]
}

resource "aws_vpc" "this" {
  cidr_block           = var.cidr
  enable_dns_hostnames = true
  tags = { Name = "${var.environment}-vpc" }
}

resource "aws_subnet" "public" {
  count             = length(var.azs)
  vpc_id            = aws_vpc.this.id
  cidr_block        = local.public_cidrs[count.index]
  availability_zone = var.azs[count.index]
  map_public_ip_on_launch = true
  tags = { Name = "${var.environment}-public-${count.index+1}", Tier = "public" }
}

output "vpc_id"          { value = aws_vpc.this.id }
output "public_subnets"  { value = aws_subnet.public[*].id }
output "private_subnets" { value = aws_subnet.private[*].id }

# Use the module
module "vpc" {
  source      = "./modules/vpc"
  cidr        = "10.0.0.0/16"
  environment = var.environment
  azs         = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

resource "aws_instance" "app" {
  subnet_id = module.vpc.private_subnets[0]  # Use module output
}

Workspaces and Environments

bash
# Workspaces = separate state per environment
terraform workspace new staging
terraform workspace new production
terraform workspace list
terraform workspace select staging

# Reference workspace in config
variable "instance_type" {
  default = {
    dev        = "t3.micro"
    staging    = "t3.medium"
    production = "m5.large"
  }
}

resource "aws_instance" "app" {
  instance_type = var.instance_type[terraform.workspace]
}

Dynamic Blocks

hcl
# Instead of repeating blocks, use dynamic
variable "ingress_rules" {
  default = [
    { port = 80,  cidr = "0.0.0.0/0",     desc = "HTTP" },
    { port = 443, cidr = "0.0.0.0/0",     desc = "HTTPS" },
    { port = 22,  cidr = "10.0.0.0/8",    desc = "SSH internal" },
  ]
}

resource "aws_security_group" "app" {
  name   = "app-sg"
  vpc_id = module.vpc.vpc_id

  dynamic "ingress" {
    for_each = var.ingress_rules
    content {
      from_port   = ingress.value.port
      to_port     = ingress.value.port
      protocol    = "tcp"
      cidr_blocks = [ingress.value.cidr]
      description = ingress.value.desc
    }
  }

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

Lifecycle Rules

hcl
resource "aws_instance" "app" {
  ami           = data.aws_ami.latest.id
  instance_type = "t3.medium"

  lifecycle {
    create_before_destroy = true   # New resource before destroying old
    prevent_destroy       = true   # Error if trying to destroy (production safety)
    ignore_changes        = [
      ami,                         # Don't replace if AMI changes
      tags["LastUpdated"],         # Ignore tag changes made by other tools
    ]
    replace_triggered_by  = [
      aws_launch_template.app.latest_version  # Replace when LT version changes
    ]
  }
}

# Null resource for running scripts
resource "null_resource" "db_migration" {
  triggers = {
    schema_hash = filesha256("migrations/schema.sql")  # Re-run when SQL changes
  }

  provisioner "local-exec" {
    command = "psql $DB_URL < migrations/schema.sql"
  }

  depends_on = [aws_db_instance.main]
}

Data Sources and Locals

hcl
# Read existing resources (not managed by this Terraform)
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["al2023-ami-2023.*-x86_64"]
  }
}

data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

# Local values — computed values used multiple times
locals {
  account_id  = data.aws_caller_identity.current.account_id
  region      = data.aws_region.current.name
  name_prefix = "${var.environment}-${var.app_name}"

  common_tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
    Project     = var.app_name
    Owner       = var.team
  }
}

resource "aws_s3_bucket" "logs" {
  bucket = "${local.name_prefix}-logs-${local.account_id}"
  tags   = local.common_tags
}

Remote State and State Locking

hcl
# backend.tf — store state in S3, lock with DynamoDB
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/eks/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    kms_key_id     = "arn:aws:kms:us-east-1:123456789:key/mrk-..."
    dynamodb_table = "terraform-state-lock"
  }
}

# Share state between configurations
data "terraform_remote_state" "vpc" {
  backend = "s3"
  config = {
    bucket = "my-terraform-state"
    key    = "prod/vpc/terraform.tfstate"
    region = "us-east-1"
  }
}

resource "aws_instance" "app" {
  subnet_id = data.terraform_remote_state.vpc.outputs.private_subnets[0]
}
Share:
Join our Community
Daily tips, job alerts, interview help — join engineers learning together
Up Next
🚀
TerraformAdvanced
Production patterns, performance, security hardening
Also Worth Exploring
← Back to all Terraform modules
FundamentalsAdvanced