June 2025
·2 min read
Infrastructure as Code with Terraform: Your First AWS Deployment
Get started with Terraform: write your first HCL configuration, deploy AWS resources, understand state, and follow the plan/apply workflow — no prior IaC experience needed.
Introduction
- Infrastructure as Code (IaC): define cloud resources in version-controlled files instead of clicking through consoles.
- Terraform: the most widely used IaC tool — provider-agnostic, declarative, state-driven.
- Goal: deploy a real AWS resource (S3 bucket + CloudFront distribution) by the end of this article.
Installing Terraform
# macOS
brew tap hashicorp/tap && brew install hashicorp/tap/terraform
# Verify
terraform version- Recommended: use
tfenvfor version management across projects (likenvmfor Node).
HCL Basics: Providers, Resources, Variables, Outputs
Provider
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-southeast-1"
}Resource
resource "aws_s3_bucket" "assets" {
bucket = "my-app-assets-prod"
tags = {
Project = "my-app"
Environment = "production"
}
}Variable
variable "environment" {
type = string
default = "production"
}
variable "bucket_name" {
type = string
}Output
output "bucket_arn" {
value = aws_s3_bucket.assets.arn
}The Plan → Apply Workflow
# Initialise — download providers
terraform init
# Preview changes (no-op)
terraform plan
# Apply changes (prompts for confirmation)
terraform apply
# Destroy all resources (careful!)
terraform destroyterraform planshows+(create),~(modify),-(destroy) — review before every apply.- Use
terraform apply -auto-approvein CI/CD only, never locally.
Terraform State
- State file (
terraform.tfstate) is Terraform's source of truth about what's deployed. - Never commit state to git — it may contain secrets.
- Remote state (see Module article): store in S3 + DynamoDB locking for team use.
First Real Deployment: S3 + CloudFront
# main.tf
resource "aws_s3_bucket" "site" {
bucket = var.bucket_name
}
resource "aws_s3_bucket_public_access_block" "site" {
bucket = aws_s3_bucket.site.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_cloudfront_origin_access_control" "default" {
name = "s3-oac"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
resource "aws_cloudfront_distribution" "cdn" {
enabled = true
origin {
domain_name = aws_s3_bucket.site.bucket_regional_domain_name
origin_id = "s3-site"
origin_access_control_id = aws_cloudfront_origin_access_control.default.id
}
default_cache_behavior {
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "s3-site"
forwarded_values {
query_string = false
cookies { forward = "none" }
}
}
restrictions { geo_restriction { restriction_type = "none" } }
viewer_certificate { cloudfront_default_certificate = true }
}
output "cloudfront_domain" {
value = aws_cloudfront_distribution.cdn.domain_name
}Variables File and .tfvars
# terraform.tfvars (gitignored for secrets; safe for non-sensitive config)
bucket_name = "my-app-assets-prod"
environment = "production"Common Commands
| Command | Purpose |
|---|---|
terraform init | Initialise providers/modules |
terraform plan | Preview changes |
terraform apply | Apply changes |
terraform output | Show output values |
terraform show | Inspect state |
terraform fmt | Format HCL files |
terraform validate | Syntax/logic check |
What Not to Do as a Beginner
- Don't manually edit
terraform.tfstate. - Don't
terraform destroyin production without a backup plan. - Don't use wildcards in IAM policies generated by Terraform — scope them (see IAM article).
- Don't store secrets in
.tfvarscommitted to git — use AWS Secrets Manager or Vault.
Conclusion
- Terraform replaces the AWS Console for infrastructure management — reproducible, reviewable, revertable.
- The
planoutput is your most valuable safety net: read it every time. - Next steps: structure multi-environment deployments with workspaces or separate state files, extract reusable modules (see: Terraform Modules article).