Introduction
Terraform is a powerful Infrastructure as Code (IaC) tool that simplifies the provisioning of cloud resources. In this blog post, I will guide you through the process of deploying a static website on AWS using Terraform. This involves creating an S3 bucket for hosting the website, making it publicly accessible, uploading the website files, and configuring CloudFront to securely serve the content. Along the way, I’ll share some of the challenges I encountered and how I overcame them.
Project Structure
The project follows a modular structure to ensure reusability and maintainability:
project-root/
├── modules/
│ └── s3-static-website/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── envs/
│ └── dev/
│ ├── main.tf
│ ├── variables.tf
│ ├── cloudfront.tf
│ ├── outputs.tf
│ ├── terraform.tfvars
│ └── backend.tf
├── website/
│ ├── index.html
│ ├── error.html
Project Architecture
Module: s3-static-website
1. Defining the S3 Bucket
The S3 bucket is configured to host the website and allow public access:
resource "aws_s3_bucket_public_access_block" "wb" {
bucket = aws_s3_bucket.wb.id
block_public_acls = true
block_public_policy = false
ignore_public_acls = true
restrict_public_buckets = false
}
resource "aws_s3_bucket_website_configuration" "wb" {
bucket = aws_s3_bucket.wb.id
index_document {
suffix = var.index_document
}
error_document {
key = var.error_document
}
}
2. Setting the Bucket Policy
To make the bucket content publicly accessible, we define a bucket policy:
resource "aws_s3_bucket_policy" "wb" {
bucket = aws_s3_bucket.wb.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.wb.arn}/*"
}
]
})
}
3. Uploading Website Files
The website files are uploaded to the bucket:
resource "aws_s3_object" "files" {
for_each = fileset(var.source_directory, "**")
bucket = aws_s3_bucket.wb.id
key = each.value
source = "${var.source_directory}/${each.value}"
acl = "public-read"
}
Environment: dev
1. Referencing the Module
The root main.tf
in the dev
folder points to the S3 website module:
provider "aws" {
region = "us-east-1"
}
module "s3_website" {
source = "../../modules/s3-static-website"
bucket_name = var.bucket_name
source_directory = "../website"
}
2. Configuring CloudFront
CloudFront is set up to securely front the S3 bucket:
resource "aws_cloudfront_distribution" "cdn" {
origin {
origin_id = "S3-${module.s3_website.bucket_name}"
domain_name = module.s3_website.regional_domain_name
}
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-${module.s3_website.bucket_name}"
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
Steps to Deploy the Website
Step 1: Define the S3 Static Website Module
Within the modules/s3-static-website/
directory:
-
main.tf:
- Provisions the S3 bucket.
- Configures public access policies.
- Uploads website files (
index.html
anderror.html
).
-
variables.tf:
- Accepts parameters like bucket name, source directory, and tags.
-
outputs.tf:
- Exports the bucket name and website endpoint.
Website Files
Ensure index.html
and error.html
are in the website/
directory to be uploaded to the S3 bucket.
Run Terraform
- Change to the
dev
directory:
cd envs/dev
- Ensure
terraform.tfvars
contains all required variables. - Initialize and apply Terraform:
terraform init
terraform apply
Applying..(wait for few minutes)
Voilà!! website successfully served on Cloudfront
Website up and running securely
Challenges Faced
1. Consuming Outputs from the Module
Initially, I faced issues with accessing module outputs in the CloudFront configuration. The error arose because I incorrectly referenced the output variables. To resolve this, I ensured the outputs were properly defined in the module and referenced using the exact variable names. For example:
-
Output Definition in
modules/s3-static-website/outputs.tf
:
output "bucket_name" {
value = aws_s3_bucket.wb.id
}
output "regional_domain_name" {
description = "The regional domain name of the bucket."
value = aws_s3_bucket.wb.bucket_regional_domain_name
}
-
Reference in
dev/cloudfront.tf
:
origin {
origin_id = "S3-${module.s3_website.bucket_name}"
domain_name = module.s3_website.regional_domain_name
}
2. Handling Backend Configuration
While setting up the Terraform backend for remote state management, I encountered permissions issues with the S3 bucket. This was resolved by reviewing the IAM policy and ensuring the required permissions (e.g., s3:PutObject
and s3:GetObject
) were granted to the Terraform user.
3. CloudFront and S3 Integration
I initially misconfigured the origin
block in the CloudFront resource. The issue was traced to using an incorrect value for domain_name
. To fix this, I ensured that the website_endpoint
output from the module was used correctly.
Conclusion
This project demonstrated how to deploy a static website on AWS with Terraform. The modular approach made it easier to reuse components and manage configurations across environments. Despite the challenges faced, such as consuming module outputs and configuring CloudFront, these hurdles provided valuable learning experiences.
With this setup, the website is securely served through CloudFront while leveraging the scalability and cost-efficiency of S3. Terraform’s declarative nature ensured the process was repeatable and predictable, making it a great tool for managing cloud infrastructure.
Top comments (0)