Deploying a frontend application shouldn't require a PhD in DevOps. In this post, I'll show you how to get a production-ready static site online with just 40 lines of Terraform.
Why Terraform for Frontend?
Infrastructure as Code (IaC) isn't just for backend engineers. When you codify your frontend infrastructure, you get:
Repeatable deployments across environments
Version-controlled infrastructure alongside your code
Easy rollbacks (just revert the Terraform state)
No manual AWS console clicking (we all hate that)
The Setup
We'll deploy a static site to AWS S3 with CloudFront CDN and Route 53 DNS. All the essentials for a production-grade frontend.
Prerequisites
bash
Install Terraform
brew install terraform # macOS
or download from terraform.io
AWS CLI configured
aws configure
The 40 Lines
Here's the entire main.tf:
hcl
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
S3 Bucket for static hosting
resource "aws_s3_bucket" "site" {
bucket = var.domain_name
}
resource "aws_s3_bucket_website_configuration" "site" {
bucket = aws_s3_bucket.site.id
index_document {
suffix = "index.html"
}
error_document {
key = "404.html"
}
}
resource "aws_s3_bucket_public_access_block" "site" {
bucket = aws_s3_bucket.site.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
resource "aws_s3_bucket_policy" "site" {
bucket = aws_s3_bucket.site.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = ""
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.site.arn}/"
}
]
})
}
CloudFront CDN
resource "aws_cloudfront_distribution" "site" {
origin {
domain_name = aws_s3_bucket_website_configuration.site.website_endpoint
origin_id = var.domain_name
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
enabled = true
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = var.domain_name
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
price_class = "PriceClass_100"
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
aliases = [var.domain_name]
custom_error_response {
error_code = 404
response_page_path = "/404.html"
response_code = 404
}
}
Variables
variable "domain_name" {
description = "Your domain name (e.g., example.com)"
type = string
}
Outputs
output "website_url" {
value = "https://${var.domain_name}"
}
output "cloudfront_domain" {
value = aws_cloudfront_distribution.site.domain_name
}
That's it. 42 lines including whitespace.
Deployment Workflow
- Initialize Terraform bash terraform init
- Plan your infrastructure bash terraform plan -var="domain_name=yourdomain.com"
- Apply the changes bash terraform apply -var="domain_name=yourdomain.com" -auto-approve
- Deploy your frontend code bash aws s3 sync ./dist s3://yourdomain.com
- Invalidate CloudFront cache (optional) bash aws cloudfront create-invalidation --distribution-id --paths "/*" CI/CD Integration Add this to your GitHub Actions workflow:
yaml
name: Deploy Frontend
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm ci && npm run build
- uses: hashicorp/setup-terraform@v2
- run: terraform init
- run: terraform apply -auto-approve -var="domain_name=${{ secrets.DOMAIN }}"
- run: aws s3 sync ./dist s3://${{ secrets.DOMAIN }}
Pro Tips
- Use Terraform State Backend Add this to store state remotely (essential for teams):
hcl
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "frontend/terraform.tfstate"
region = "us-east-1"
}
}
- Environment Separation Use Terraform workspaces or separate terraform.tfvars files:
hcl
terraform.tfvars.prod
domain_name = "prod.example.com"
terraform.tfvars.staging
domain_name = "staging.example.com"
- Add Custom Headers with CloudFront Functions hcl resource "aws_cloudfront_function" "security_headers" { name = "security-headers" runtime = "cloudfront-js-1.0" code = <<EOF function handler(event) { var response = event.response; response.headers['strict-transport-security'] = { value: 'max-age=63072000' }; response.headers['x-frame-options'] = { value: 'SAMEORIGIN' }; return response; } EOF } What About Other Cloud Providers? The same principle applies. Here's a GCP example:
hcl
resource "google_storage_bucket" "site" {
name = var.domain_name
location = "US"
website {
main_page_suffix = "index.html"
not_found_page = "404.html"
}
}
resource "google_storage_bucket_iam_binding" "public" {
bucket = google_storage_bucket.site.name
role = "roles/storage.objectViewer"
members = ["allUsers"]
}
Real-World Results
Using this approach, I've:
Reduced deployment time from 15 minutes to 2 minutes
Eliminated manual errors (no more "oops, I forgot to enable CDN")
Made rollbacks instant (terraform apply with a previous state)
Saved $50/month by automating resource cleanup with terraform destroy
The Big Picture
Infrastructure as Code for frontend isn't just about deploying faster—it's about treating your infrastructure with the same rigor as your application code. When your infrastructure is in version control, you can:
Review changes via PRs
Track who changed what and why
Reproduce environments exactly
Audit for security compliance
Next Steps
Add SSL certificates with AWS ACM
Implement custom error pages
Set up CloudFront Functions for A/B testing
Add monitoring with CloudWatch alarms
Implement Blue/Green deployments
Final Thoughts
Forty lines of Terraform replaced hours of manual AWS console clicking, created repeatable deployments, and gave me infrastructure that's actually maintainable. Your frontend deserves the same reliability as your backend.
Remember: The goal isn't to write the least lines of code—it's to create infrastructure that's simple, maintainable, and predictable. Forty lines just happens to be how simple it can be.
What's your experience with IaC for frontend? Drop a comment below!
Top comments (0)