DEV Community

Cover image for Deploying a Static Website on AWS S3 with Terraform: A Beginner's Guide
Otu Udo
Otu Udo

Posted on

Deploying a Static Website on AWS S3 with Terraform: A Beginner's Guide

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
Enter fullscreen mode Exit fullscreen mode

Project Architecture

Image description

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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}/*"
      }
    ]
  })
}
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

Steps to Deploy the Website

Step 1: Define the S3 Static Website Module

Within the modules/s3-static-website/ directory:

  1. main.tf:

    • Provisions the S3 bucket.
    • Configures public access policies.
    • Uploads website files (index.html and error.html).
  2. variables.tf:

    • Accepts parameters like bucket name, source directory, and tags.
  3. 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

  1. Change to the dev directory:
   cd envs/dev
Enter fullscreen mode Exit fullscreen mode
  1. Ensure terraform.tfvars contains all required variables.
  2. Initialize and apply Terraform:
   terraform init
   terraform apply
Enter fullscreen mode Exit fullscreen mode

Image description

Applying..(wait for few minutes)

Image description

Voilà!! website successfully served on Cloudfront

Image description

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
}
Enter fullscreen mode Exit fullscreen mode
  • Reference in dev/cloudfront.tf:
  origin {
    origin_id   = "S3-${module.s3_website.bucket_name}"
    domain_name = module.s3_website.regional_domain_name
  }
Enter fullscreen mode Exit fullscreen mode

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)