Hosting a Static Website
S3 can host a static website. You just need to change a few configurations on the bucket to turn an S3 bucket into a host for your website.
CodeBuild is a fully managed continuous integration service that compiles source code, runs tests, and produces software packages that are ready to deploy. In order to tell CodeBuild what to do and how to do things, we need to provide a special YAML file named buildspec.yaml. One of the great features of CodeBuild is its ability to run any code in the cloud environment. We will be using this feature to upload our website's content to S3. CodeBuild can also connect to a GitHub repo and listen to changes applied there. It can then run any code once a change is detected (like when you push a new update for your website).
With just S3 and CodeBuild, we can create a CI/CD pipeline to host a static website. We'll be demonstrating this first manually in the class. Then we'll be using Terraform to automate the process, so we can easily spin up a pipeline for any static website in a minute in the future. What follows next is the Terraform code needed for creating the pipeline.
main.tf
# create S3 bucket to host the website
resource "aws_s3_bucket" "site_bucket" {
bucket = var.bucket_name
}
# make sure public access on the bucket is not blocked
resource "aws_s3_bucket_public_access_block" "this" {
bucket = aws_s3_bucket.site_bucket.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
# create bucket policy to allow public read access
# without this, no one would be able to view the website
resource "aws_s3_bucket_policy" "allow_public_access" {
bucket = aws_s3_bucket.site_bucket.id
policy = <<POLICY
{
"Version": "2012-10-17",
"Id": "Policy1664159397662",
"Statement": [
{
"Sid": "Stmt1664159396184",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "${aws_s3_bucket.site_bucket.arn}/*"
}
]
}
POLICY
}
# configure the S3 bucket as a statuc website
resource "aws_s3_bucket_website_configuration" "my_website" {
bucket = aws_s3_bucket.site_bucket.id
index_document {
suffix = var.index_document
}
}
# create a CodeBuild project
resource "aws_codebuild_project" "project" {
name = var.project_name
build_timeout = "20"
service_role = aws_iam_role.cbuild_role.arn
artifacts {
type = "NO_ARTIFACTS"
}
environment {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/standard:4.0"
type = "LINUX_CONTAINER"
# passing the bucket name as an environment variable
environment_variable {
name = "BUCKET_NAME"
value = "s3://${var.bucket_name}"
}
}
logs_config {
cloudwatch_logs {
group_name = "build-${var.project_name}"
}
}
# set the source to our repo
source {
type = "GITHUB"
location = var.repo_address
git_clone_depth = 1
}
# set the branch name for the pipeline
source_version = var.branch_name
}
# create a role for CodeBuild service to assume
resource "aws_iam_role" "cbuild_role" {
name = var.project_name
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "codebuild.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
# attach an IAM policy to CodeBuild role
# for accessing the S3 bucket
resource "aws_iam_role_policy" "this" {
role = aws_iam_role.cbuild_role.name
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Resource": [
"*"
],
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": ["${aws_s3_bucket.site_bucket.arn}", "${aws_s3_bucket.site_bucket.arn}/*"]
}
]
}
POLICY
}
# create a webhook so the projects can detect
# changes in the repo and start a new build
# note that you need to remove this section (or comment out)
# and build whatever's above this resource first
# you then need to change the source section of the
# CodeBuild project to connect to your GitHub repo
# after that, you can create this resource
resource "aws_codebuild_webhook" "this" {
project_name = aws_codebuild_project.project.name
build_type = "BUILD"
filter_group {
filter {
type = "EVENT"
pattern = "PUSH"
}
filter {
type = "HEAD_REF"
pattern = var.branch_name
}
}
}
variables.tf
variable "bucket_name" {
type = string
}
variable "index_document" {
type = string
default = "index.html"
}
variable "project_name" {
type = string
}
variable "repo_address" {
type = string
}
variable "branch_name" {
type = string
default = "main"
}
terraform.tfvars
bucket_name = "my-awesome-website-32989"
repo_address = "<REPO-LINK>"
project_name = "awesome-website"
outputs.tf
# output the address of the static website
output "website_address" {
value = aws_s3_bucket_website_configuration.my_website.website_endpoint
}
versions.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
provider "aws" {
region = "ca-central-1"
}
buildspec.yml
version: 0.2
phases:
build:
commands:
- aws s3 sync . $BUCKET_NAME --exclude ".git/*"
post_build:
commands:
- echo Success!