ποΈ Terraform Infrastructure as Code
Terraform by HashiCorp is the industry-standard Infrastructure as Code (IaC) tool. It lets you define cloud infrastructure in a declarative configuration language (HCL), plan changes before applying them, and maintain state to track what has been provisioned.
Core Concepts
- Providers β Plugins that interface with APIs (AWS, GCP, Azure, Kubernetes, GitHub, etc.).
- Resources β The fundamental unit. Each resource maps to a real infrastructure object (e.g.,
aws_instance). - Data Sources β Query existing infrastructure managed outside Terraform.
- State β A JSON file (local or remote) that maps your configuration to real-world resources.
- Modules β Reusable, parameterized collections of resources.
- Workspaces β Isolated state environments for the same configuration (dev/staging/prod).
HCL Syntax
HashiCorp Configuration Language (HCL) is Terraform's declarative syntax. It is designed to be human-readable and machine-writable.
hcl
# variables.tf
variable "environment" {
description = "Deployment environment (dev, staging, prod)"
type = string
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be dev, staging, or prod."
}
}
variable "instance_count" {
description = "Number of EC2 instances"
type = number
default = 2
}
variable "allowed_cidrs" {
description = "CIDR blocks allowed to reach the load balancer"
type = list(string)
default = ["0.0.0.0/0"]
}
# locals.tf
locals {
common_tags = {
Project = "mc-platform"
Environment = var.environment
ManagedBy = "terraform"
Owner = "platform-team"
}
name_prefix = "mc-${var.environment}"
}
Providers & Backend Configuration
hcl
# versions.tf
terraform {
required_version = ">= 1.7.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.25"
}
}
# Remote state in S3 with DynamoDB locking
backend "s3" {
bucket = "mc-terraform-state-prod"
key = "platform/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "mc-terraform-locks"
kms_key_id = "alias/terraform-state"
}
}
provider "aws" {
region = "us-east-1"
default_tags {
tags = local.common_tags
}
}
provider "aws" {
alias = "us-west-2"
region = "us-west-2"
}
State Management
Never commit state files Always use a remote backend (S3, Terraform Cloud, GCS) with state locking. Local state files can cause irreversible infrastructure drift when shared on teams.
bash
# State operations
terraform state list # list all managed resources
terraform state show aws_instance.web_server # inspect a specific resource
terraform state mv aws_instance.old aws_instance.new # rename resource
terraform state rm aws_instance.imported # stop managing (does NOT destroy)
# Import existing infrastructure
terraform import aws_s3_bucket.assets mc-assets-prod
# Refresh state to match real-world (use carefully)
terraform apply -refresh-only
Modules
Modules encapsulate reusable infrastructure patterns. Maxi's Computers maintains an internal module registry for common components.
hcl
# main.tf β using internal modules
module "vpc" {
source = "git::https://github.com/maxiscomputers/terraform-modules.git//vpc?ref=v3.2.0"
name = "${local.name_prefix}-vpc"
cidr_block = "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
single_nat_gateway = var.environment != "prod"
tags = local.common_tags
}
module "eks" {
source = "git::https://github.com/maxiscomputers/terraform-modules.git//eks?ref=v2.1.0"
cluster_name = "${local.name_prefix}-eks"
cluster_version = "1.29"
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnet_ids
node_groups = {
general = {
instance_types = ["m6i.xlarge"]
min_size = 2
max_size = 10
desired_size = 3
}
}
}
# outputs.tf
output "vpc_id" { value = module.vpc.vpc_id }
output "eks_endpoint" { value = module.eks.cluster_endpoint }
output "eks_ca_data" { value = module.eks.cluster_ca_certificate }
Workspaces
bash
# Create and switch workspaces
terraform workspace new dev
terraform workspace new staging
terraform workspace new prod
terraform workspace list
terraform workspace select prod
# Use workspace name in configuration
resource "aws_s3_bucket" "app_data" {
bucket = "mc-app-${terraform.workspace}-data"
# ...
}
Standard CLI Workflow
bash
# 1. Initialize β download providers and modules
terraform init -upgrade
# 2. Format and validate
terraform fmt -recursive
terraform validate
# 3. Plan β preview changes (always review before applying!)
terraform plan -var-file="environments/prod.tfvars" -out=tfplan
# 4. Apply β execute the plan
terraform apply tfplan
# 5. Targeted apply (use sparingly)
terraform apply -target=module.eks -var-file="environments/prod.tfvars"
# 6. Destroy (DANGER β requires explicit approval)
terraform destroy -var-file="environments/prod.tfvars"
Best Practices at MC
- Pin provider versions with
~>constraints to avoid unexpected breaking changes. - Use
terraform planin CI β post the plan output as a pull request comment before merging. - Store
.tfvarssecurely β never commit sensitive values; use CI secrets or Vault integration. - Tag every resource with
local.common_tagsfor cost allocation and compliance. - Run
terraform fmtas a pre-commit hook to enforce consistent formatting. - Use
-outflag so the plan applied is exactly what was reviewed. - Separate state per environment β never share a single state file across prod/staging/dev.