WebPiki
tutorial

Getting Started with Terraform and IaC

Infrastructure as Code concepts, Terraform basics, workflow, state management, and a practical VPC + EC2 example.

Infrastructure being stacked like building blocks with IaC

Log into the AWS console, spin up an EC2 instance, configure a security group, create an RDS database, connect a VPC... clicking through the console UI works fine the first time. But what happens when you need to replicate the same environment for staging? Or deploy to another region? Or remember how everything is configured three months from now?

Infrastructure as Code (IaC) defines infrastructure in code files. Write "one t3.medium EC2 instance, security group configured like this, VPC like that" in a file, and a tool reads it and creates the actual infrastructure.

What IaC Gives You

Reproducibility — Same code, same infrastructure. No more "which option did I click in the console?" moments. Dev, staging, and production can stay identical.

Version control — It's code, so it goes in Git. Infrastructure changes show up in commit history. Who changed what and when is fully traceable. Roll back to a previous version if something breaks.

Automation — Integrate it into CI/CD pipelines. Code review before infrastructure changes get applied. Fewer human errors compared to manual console work.

Documentation — The infrastructure configuration is documented in the code itself. Less need for separate docs, and the code is the source of truth for the current state.

Terraform Basics

Terraform is an IaC tool by HashiCorp. It supports hundreds of providers — AWS, Azure, GCP, but also Cloudflare, Datadog, GitHub, and many more. Not being locked into a single cloud is a major advantage.

It uses HCL (HashiCorp Configuration Language), which feels somewhere between JSON and YAML.

Provider

Tells Terraform which cloud platform you're using.

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

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

~> 5.0 means any 5.x version but below 6.0. Minor updates are allowed; major version bumps require explicit changes.

Resource

The building blocks of your infrastructure. EC2 instances, S3 buckets, VPCs, security groups.

resource "aws_instance" "web" {
  ami           = "ami-0c9c942bd7bf113a2"
  instance_type = "t3.micro"

  tags = {
    Name = "web-server"
  }
}

"aws_instance" is the resource type. "web" is the local name you use to reference this instance elsewhere as aws_instance.web.

Variable

Lets you inject values from outside. Useful for environment-specific configuration.

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

variable "environment" {
  description = "Deployment environment"
  type        = string
  # No default means Terraform will prompt for input
}

resource "aws_instance" "web" {
  ami           = "ami-0c9c942bd7bf113a2"
  instance_type = var.instance_type

  tags = {
    Name        = "web-server"
    Environment = var.environment
  }
}

Pass variable values via terraform.tfvars files, environment variables (TF_VAR_xxx), or CLI flags (-var).

Output

Prints useful information after resource creation, like an EC2 instance's public IP.

output "web_public_ip" {
  description = "Public IP of the web server"
  value       = aws_instance.web.public_ip
}

Terraform Workflow

Four commands form the core loop.

terraform init

Project initialization. Downloads provider plugins. Think of it as npm install for infrastructure. Run it when starting a new project or adding a provider.

terraform plan

Compares your code against actual infrastructure and shows what would change — without making any changes.

$ terraform plan

Terraform will perform the following actions:

  # aws_instance.web will be created
  + resource "aws_instance" "web" {
      + ami                          = "ami-0c9c942bd7bf113a2"
      + instance_type                = "t3.micro"
      + ...
    }

Plan: 1 to add, 0 to change, 0 to destroy.

+ means create, ~ means modify, - means delete. Review this output, confirm it matches your intent, then apply. Skipping plan and going straight to apply is risky.

terraform apply

Executes the changes shown in plan. Prompts for confirmation before proceeding.

terraform destroy

Deletes all resources managed by Terraform. Useful for tearing down test environments. Use with extreme caution in production.

State — Terraform's Memory

Terraform stores the current infrastructure state in terraform.tfstate. This file is Terraform's record of "what the infrastructure looks like right now."

If your code has aws_instance.web and the state file also has aws_instance.web, Terraform knows the resource exists and checks if its attributes have changed. If it's not in the state, Terraform treats it as a new resource to create.

Why Local State Is a Problem

When a team shares a project and everyone has their own local state file, things break. Person A creates an EC2 instance, but person B's state doesn't know about it. Person B runs apply and tries to create a duplicate.

The solution: store state remotely. AWS S3 + DynamoDB is the standard approach. S3 holds the state file; DynamoDB provides locking to prevent concurrent modifications.

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

State files can contain sensitive data. Database passwords appear in plain text. Always enable encryption on S3 and tightly control access permissions.

Modules — Reusable Code

When you need the same infrastructure pattern multiple times, wrap it in a module. Similar concept to functions.

# modules/vpc/main.tf
variable "cidr_block" {
  type = string
}

variable "name" {
  type = string
}

resource "aws_vpc" "this" {
  cidr_block = var.cidr_block
  tags = {
    Name = var.name
  }
}

output "vpc_id" {
  value = aws_vpc.this.id
}
# Usage
module "prod_vpc" {
  source     = "./modules/vpc"
  cidr_block = "10.0.0.0/16"
  name       = "prod-vpc"
}

module "staging_vpc" {
  source     = "./modules/vpc"
  cidr_block = "10.1.0.0/16"
  name       = "staging-vpc"
}

The Terraform Registry has plenty of community modules for common patterns — VPCs, EKS clusters, and more. When using external modules, read the code and pin the version.

Practical Example: VPC + EC2

A minimal setup: create a VPC with a public subnet and launch an EC2 instance in it.

# VPC
resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags = { Name = "main-vpc" }
}

# Public Subnet
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "us-east-1a"
  tags = { Name = "public-subnet" }
}

# Internet Gateway
resource "aws_internet_gateway" "gw" {
  vpc_id = aws_vpc.main.id
  tags   = { Name = "main-igw" }
}

# Route Table
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.gw.id
  }

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

resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

# Security Group
resource "aws_security_group" "web" {
  name_prefix = "web-"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["203.0.113.0/32"]  # Your IP only
  }

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

# EC2 Instance
resource "aws_instance" "web" {
  ami                    = "ami-0c9c942bd7bf113a2"
  instance_type          = "t3.micro"
  subnet_id              = aws_subnet.public.id
  vpc_security_group_ids = [aws_security_group.web.id]
  tags = { Name = "web-server" }
}

Notice the references between resources (aws_vpc.main.id, aws_subnet.public.id). Terraform automatically figures out the dependency graph and creates resources in the correct order.

Comparison with Other IaC Tools

AWS CloudFormation — AWS-only. Written in JSON or YAML. Deep integration with AWS services and built-in stack rollback. Downsides: no multi-cloud support, and templates tend to get verbose.

Pulumi — Similar to Terraform but uses general-purpose languages (TypeScript, Python, Go, C#) instead of HCL. Conditionals, loops, and functions come naturally. The trade-off is losing some of HCL's declarative simplicity.

AWS CDK — Write CloudFormation in TypeScript, Python, etc. It compiles down to CloudFormation templates. Good fit if you're all-in on AWS.

Terraform has the broadest ecosystem and widest applicability. If you're starting with IaC for the first time, Terraform is the most practical entry point.

Practical Tips

Commit .terraform.lock.hcl. This lock file pins provider versions, ensuring everyone on the team uses the same versions. Same role as package-lock.json.

Put terraform plan in CI. When a PR includes infrastructure changes, have the plan output posted as a comment. Atlantis is a popular tool for this.

Separate directories per environment. environments/prod/, environments/staging/ — same modules, different variables. Consistent infrastructure with independent management.

Never edit state manually. If state gets corrupted, terraform state commands can fix it, but mistakes can destroy infrastructure. Last resort only.

Be careful with destroy. terraform destroy deletes everything Terraform manages. In production, add lifecycle { prevent_destroy = true } to critical resources and make sure destroy never runs automatically in CI.

The initial setup takes some time, but IaC pays off as infrastructure grows. When a task that took 30 minutes of console clicking becomes a one-line code change, you'll wonder why you didn't start sooner.

#Terraform#IaC#DevOps#Cloud#Infrastructure

관련 글