AWS S3 Bucket Website Hosting using Terraform
Tags: aws, terraform, githubactions, s3
Published: 2024-06-27 03:59:47

In previous blog [Deploy Terraform resources to AWS using GitHub Actions via OIDC](https://dev.to/camillehe1992/deploy-terraform-resources-to-aws-using-github-actions-via-oidc-3b9g), I explained how to configure OpenID Connect within GitHub Actions workflows to authenticate with AWS, then demonstrated the process using a very simple Actions workflow that lists all buckets in my AWS account.  As I mentioned, the common use case in real world is we define AWS infrastructure as code, provision AWS resources automatically and manage these cloud infrastructure in GitHub. 

In this article, I’ll go one step further. Use GitHub Actions workflow to provision and manage a S3 website using S3 website static hosting feature. The S3 bucket related AWS infrastructure is defined using Terraform. The website content and Terraform code are saved in GitHub. Any code change will trigger a new workflow build automatically to sync its infrastructure status in AWS. After done, your website can be available through bucket static website endpoint from browser.

![arch-diagrm](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/43cy10umdco14gc2v1p5.png)

The whole process includes three sections:
1. Infrastructure as Code: Define AWS infrastructure as code using Terraform.
2. Website Static Content: Create index.html and 404.html files as the website static content.
3. GitHub Actions workflow: Create workflow to provision AWS infrastructure to AWS.

Let's get started.

## Prerequisites

### 1. Select Tools

**Terraform**: I choose [Terraform](https://www.terraform.io/) to provision and manage AWS infrastructure. You can use any other tools as you want, such as CloudFormation, CDK etc. The core concept is the same. Define your cloud infrastructure as code.
**GitHub**: Manage source code in a version control system, such as GItHub, Bitbucket, AWS CodeCommit, or Gitlab, etc.
**GitHub Actions**: CICD pipeline management tool, such as GitHub Actions, Jenkins, Bitbucket pipeline, AWS CodeBuild, etc.

### 2. Create Terraform Backend S3 Bucket

As Terraform uses persisted state data to keep track of the resources it manages, we use a backend to store state remotely. S3 bucket is commonly used to save the state of Terraform infrastructure in AWS. You can create a S3 bucket manually from AWS console, Click on Create bucket button, enter a meaningful bucket name, to make it simple, just keep all configuration as default, and click Create bucket. 

The bucket name will be used in Step 3 workflow environment variable **TF_BACKEND_S3_BUCKET**.

### 3. Attach Policy on Deployment IAM Role

Remember we created a dedicated IAM role for deployment named **GitHubAction-AssumeRoleWithAction** in previous blog. In order to provision all AWS infrastructure that we used in this demo, you should add polices on the role. The easiest way is to attach an AWS managed policy **AmazonS3FullAccess** which grants full access to all buckets in your AWS account.

After done, move to coding part. You can find the sample code from GitHub repo: https://github.com/camillehe1992/demo-for-aws-deployment-via-oidc

## Step 1. Infrastructure as Code

I use Terraform to define all AWS infrastructure as code, so that AWS resources can be provisioned automatically through Actions workflow and be managed in GitHub. I won’t dive into the Terraform code, as that's not in the scope. All terraform related files are in terraform directory as below.

```bash
└── terraform
    ├── local.tf
    ├── main.tf
    ├── mime.json
    ├── outputs.tf
    ├── prod.tfvars
    ├── providers.tf
    └── variables.tf
```

### Step 2. Website Static Content
 
Now, let’s prepare our demo website content. In public directory, create index.html, 404.html and image files. All the files in the directory are uploaded to S3 bucket as the website static content.

```bash
├── public
│   ├── 404.html
│   ├── images
│   │   ├── coffee.jpg
│   │   └── dogs.jpg
│   └── index.html
```

### Step 3. GitHub Actions Workflow

I created a new Actions workflow named **deploy.yaml** in **.github/workflows** directory. 

```bash
├── .github
│   └── workflows
│       ├── deploy.yaml
│       └── get-started.yaml
...
```

Comparing with **get-started.yaml** workflow, Here are main updates:
- Add new environment variables in env block and configure these variables in GitHub Settings -> Secrets and variables -> Variable -> repository variable.

```yaml
env:
  ...
  TF_BACKEND_S3_BUCKET: ${{ vars.TF_BACKEND_S3_BUCKET }}
  ENVIRONMENT: prod
  NICKNAME: demo-for-aws-deployment-via-oidc
```

![add-variable](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yw1veitrq0fj702gxul9.png)

**TF_BACKEND_S3_BUCKET**: the S3 bucket name of Terraform state files.
**ENVIRONMENT**: is part of Terraform backend S3 object key. Meanwhile, it's used as the suffix of website bucket name.
**NICKNAME**: is part of Terraform backend S3 object key.

- Add three steps in job block after authentication:

```yaml
      - name: Terraform init
        working-directory: terraform
        run: |
          terraform init -reconfigure \
            -backend-config="bucket=$TF_BACKEND_S3_BUCKET" \
            -backend-config="region=$AWS_REGION" \
            -backend-config="key=$NICKNAME/prod/$AWS_REGION/terraform.tfstate"
      # An exit code of 0 indicated no changes, 1 a terraform failure, 2 there are pending changes.
      - name: Terraform plan
        id: tf-plan
        working-directory: terraform
        run: |
          export exitcode=0

          terraform plan \
            -var-file=$ENVIRONMENT.tfvars -detailed-exitcode -no-color -out tfplan || export exitcode=$?

          echo "exitcode=$exitcode" >> $GITHUB_OUTPUT

          if [ $exitcode -eq 1 ]; then
            echo Terraform Plan Failed!
            exit 1
          else
            exit 0
          fi
      # Apply the pending changes
      - name: Terraform apply
        if: ${{ steps.tf-plan.outputs.exitcode == 2 }}
        working-directory: terraform
        run: |
          terraform apply -auto-approve tfplan -no-color
```
Steps:
**Terraform init**: Run terraform init CLI with backend configuration
**Terraform plan**: Run terraform plan CLI to generate a plan.
**Terraform apply**: Run terraform apply CLI to apply the plan if there is pending change on it.

Commit and push to remote. A new workflow appears in Actions -> workflow named **Deploy Static Website** with running build.

![deploy-workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s3afhgpgzv571g2uttve.png)

Here is the detail steps:

![deploy-workflow-detail-steps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fl7lsw0b3j38nv4p9tom.png)

All content in S3 bucket:

![s3-objects](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8gbsa3zhoem6uu3sdheb.png)

You can find the _website_endpoint_ from the end of Terraform apply logs. Or go to AWS console, find your website bucket -> Properties. Scroll to the bottom of the page, then you will find the Bucket website endpoint. Depending on your Region, your Amazon S3 website endpoint follows one of these two formats.

* http://bucket-name.s3-website-Region.amazonaws.com
* http://bucket-name.s3-website.Region.amazonaws.com

You can visit your website from browser now. Add path after the endpoint that doesn't exist, an 404 page is returned.

Correct URL:

![coffee-page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5llk9o2ckasiu6n7cmh7.png)

Incorrect URL:

![404-page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oew9dyk53plh4kost7sy.png)

## Summary

You should know how to provision and manage AWS infrastructure using GitHub and Terraform. Now you can provision more AWS services or even other Cloud infrastructure as you want following the same methodology.

## References
https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html

Thanks for reading!