CI/CD & DevOps
Infrastructure as Code Basics
Define your infrastructure in version-controlled configuration files for reproducibility and auditability.
What Infrastructure as Code Is
Infrastructure as Code (IaC) means defining your servers, databases, networks, and cloud resources in configuration files that are version-controlled, peer-reviewed, and applied automatically — exactly like application code.
Without IaC: infrastructure is configured by clicking through cloud consoles. Changes are undocumented, hard to reproduce, and easy to get wrong.
With IaC: infrastructure changes go through code review. Every change is documented in git history. You can recreate the entire infrastructure from scratch in minutes.
IaC Tools
Terraform — The most widely adopted IaC tool. Cloud-agnostic (works with AWS, GCP, Azure, Vercel, and hundreds of providers). Declarative: you describe the desired state, Terraform figures out how to achieve it.
Pulumi — Uses real programming languages (TypeScript, Python, Go) instead of a custom syntax. Appealing to developers who find YAML/HCL limiting.
AWS CDK — AWS-specific IaC using TypeScript or Python. Generates CloudFormation templates. Excellent if you are AWS-only.
Docker + Docker Compose — IaC at the application level. Your Dockerfile and docker-compose.yml are IaC for application infrastructure.
Terraform Basics
# main.tf
# Configure the AWS provider
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# Variables
variable "aws_region" {
default = "us-east-1"
}
variable "environment" {
type = string
}
# An S3 bucket for static assets
resource "aws_s3_bucket" "assets" {
bucket = "myapp-assets-${var.environment}"
tags = {
Environment = var.environment
ManagedBy = "Terraform"
}
}
# Make the bucket public (for static assets like images)
resource "aws_s3_bucket_public_access_block" "assets" {
bucket = aws_s3_bucket.assets.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
# Output the bucket name
output "assets_bucket_name" {
value = aws_s3_bucket.assets.bucket
}The Terraform Workflow
# Initialize (download providers)
terraform init
# Preview changes (shows what will be created/modified/destroyed)
terraform plan
# Apply changes (creates/modifies/destroys resources)
terraform apply
# Destroy all resources
terraform destroyAlways run terraform plan before terraform apply. Review every planned change — destroyed resources cannot be recovered.
When IaC Is Overkill
IaC has real overhead: learning the tool, managing state, debugging provider issues. It pays off at scale, but for small projects:
- Solo project on Vercel? You don't need Terraform. Vercel's UI is perfectly sufficient.
- Single Supabase database? Use Supabase's dashboard or migrations.
- Early prototyping? Move fast, use consoles, add IaC when the project stabilizes.
The right time to add IaC is when: you have a team and need infrastructure changes reviewed, you're managing multiple environments (dev/staging/prod), or you need disaster recovery capability.
Key Takeaways
- IaC defines infrastructure in version-controlled files that are reproducible and peer-reviewed
- Terraform is the most widely used tool — cloud-agnostic, declarative, large ecosystem
- Always run
terraform planbeforeterraform applyto review what will change - Docker Compose is IaC at the application level — already in use for most teams
- IaC is not necessary for every project — Vercel, Netlify, and Supabase UIs are IaC enough for many applications
Example
# Terraform: S3 bucket + CloudFront CDN
terraform {
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
}
provider "aws" { region = "us-east-1" }
resource "aws_s3_bucket" "static_assets" {
bucket = "myapp-static-assets"
}
resource "aws_cloudfront_distribution" "cdn" {
origin {
domain_name = aws_s3_bucket.static_assets.bucket_regional_domain_name
origin_id = "S3Origin"
}
enabled = true
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3Origin"
viewer_protocol_policy = "redirect-to-https"
forwarded_values {
query_string = false
cookies { forward = "none" }
}
}
restrictions {
geo_restriction { restriction_type = "none" }
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
output "cdn_url" {
value = aws_cloudfront_distribution.cdn.domain_name
}