Why Infrastructure as Code?

Clicking through the AWS console is fine for experimenting. It's a disaster for production. You can't version it, review it, or reproduce it reliably. That's exactly what Terraform solves — you describe your infrastructure in code, commit it to Git, and apply it the same way everywhere.

Core Terraform Concepts

  • Provider — the platform you're targeting (AWS, GCP, Azure, etc.)
  • Resource — a piece of infrastructure to create (EC2 instance, S3 bucket, VPC)
  • State — Terraform's record of what it has created
  • Plan — a preview of changes before applying them

Step 1: Install and Configure Terraform

# macOS
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# Verify
terraform version

# Configure AWS credentials
aws configure
# Enter your AWS Access Key ID, Secret, and default region

Step 2: Your First Terraform Project

Create a directory and a main.tf file:

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

provider "aws" {
  region = "eu-central-1"
}

# Create an S3 bucket
resource "aws_s3_bucket" "devops_artifacts" {
  bucket = "devopspack-artifacts-${random_id.suffix.hex}"

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

resource "random_id" "suffix" {
  byte_length = 4
}

# Output the bucket name
output "bucket_name" {
  value = aws_s3_bucket.devops_artifacts.bucket
}

Step 3: Init, Plan, Apply

# Download providers
terraform init

# Preview what will be created (no changes made)
terraform plan

# Create the resources
terraform apply

# Type 'yes' when prompted

Terraform will print the bucket name as output. Check your AWS console — the bucket is there.

Step 4: Remote State with S3 Backend

Never store state locally in a team environment. Use S3 + DynamoDB for locking:

terraform {
  backend "s3" {
    bucket         = "my-terraform-state-bucket"
    key            = "prod/terraform.tfstate"
    region         = "eu-central-1"
    dynamodb_table = "terraform-locks"
    encrypt        = true
  }
}

Step 5: Organize with Modules

As your infra grows, group related resources into modules:

modules/
  vpc/
    main.tf
    variables.tf
    outputs.tf
  ec2/
    main.tf
    variables.tf
    outputs.tf

Reference a module like this:

module "vpc" {
  source = "./modules/vpc"
  cidr_block = "10.0.0.0/16"
  environment = "production"
}

Best Practices

  • Always run terraform plan before apply — treat it like a code review
  • Store state remotely — never commit .tfstate files to Git
  • Use workspaces or separate state files for dev/staging/prod environments
  • Tag every resource with Environment, ManagedBy, and Owner
  • Lock provider versions to avoid surprise breaking changes