In this post, we are deploying our static websites (e.g js, css, html) to a S3 Bucket to allow for higher performance and global distribution with lower costs.


S3 is an awesome object storage, but is also a great server for static websites.

Alongside Cloudfront as our Content Distribution Network with low maintenance, and with SSL out of the box with AWS ACM.

Easy to deploy new assets, as easy as folder copy.

Cheaper than an instance, or even a droplet as I used to host this blog, atleast for low traffic websites.

S3 and Cloudfront have great performance.


  • AWS account

  • Terraform (v0.12+)

  • aws-cli

  • Docker (Optional: to generate our static sites without polluting our workspace)

  • Domain (Optional: to use our custom domain instead of using an aws provided one with some random chars, this can be from any registrar, even route53 itself)

Manually pointing our Domain to our Route53

First you must create your Route53 hosted route first, in order to use your custom domain, if you have none.

So navigate to :

And create a new hosted zone:

After creating your hosted zone, you must update the nameservers(ns) that were created for this zone in your current registrar.

Creating your Cloudfront and S3 Buckets

Now that we’ve created our hosted zone for our domain and instead of reinventing the wheel, we will be using an update version of David Newman “terrablog module” modified to be terraform 0.12 compatible.

The module we will be using is: Static Website Terraform

So let’s create our terraform file. We will just create a for simplicity as we will reuse the module.

provider "aws" {
  profile = "default"
  region  = "us-east-1"

#Optional - If you wish to keep track of your current state instead of a local machine
terraform {
  backend "s3" {
    region         = "us-east-1"
    bucket         = "my-state-bucket"
    key            = "terraform.tfstate"
    dynamodb_table = "my-state-lock"
    encrypt        = true

module "static_website" {
  source = ""
  site_bucket_name = "name-for-our-s3-bucket"
  domain_name = ""

So, besides the provider, and the optional backend to keep our state, let’s understand the variables we are passing. Both site_bucket_name and domain_name are pretty self-explanatory, the first is the bucket name that will hold our assets, and the second one is the domain we will use with both our certificate and the cloudfront distribution.

The cdn_price_class refers to the type of our distribution that we want for our cloudfront. Different pice classes, have global, or regional distribution, with global price classes having higher prices for traffic incoming to our website.

In order to know more, and to find out which price class best suits you, visit aws amazon cloudfront pricing and details page here

The possible values are PriceClass_All, PriceClass_200, PriceClass_100, and you may check more info on the cloudfront_distribution terraform resource here.

Ok, so when you are comfortable with it, lets apply our terraform.

#We must first initialize terraform in our current folder
terraform init
#first lets plan to check everything is ok
terraform plan
#Now lets appply it
terraform apply

This should take a long time, as the cloudfront distribution takes around 15 minutes to apply, and it will take even more to validate the created TLS certificate if you just changed your domain nameservers to point to the newly created hosted zone.

Creating your static assets (Docker optional)

By now our infrastructure should be all set.

Since its out of scope a web application is not provided. For instance, you may use the following command to generate a sample app for react.

npx create-react-app my-app


Or you may use just a simple index.html and skip npm alltogether, its your choice.

Let’s then use a docker container to generate our assets for a sample vue application.

Let’s use the following dockerfile:

FROM node:10-alpine AS builder
RUN apk --update add git
WORKDIR /home/node/app
COPY . .
RUN npm install
RUN npm ci
ENV NODE_ENV=production
RUN npm run build

So now let’s build our image using docker

docker build -t app .

And lets run it to extract our generated assets. It doesn’t matter what you run, you are only interested in extracting the assets.

docker run --name assets app ls

And to copy the generated files

docker cp assets:/home/node/app/dist site
#And lets clean the containers
docker rm assets

If you wish, you may only run the npm ci, and npm run build commands directly if you have node and npm installed locally without resorting to a Docker image.

Ok, so now you may see that under site, its your generated assets, including the index.html

All we are missing is the deployment.

Deploying you static assets to S3 bucket

So, your deployment is as simple as using the aws cli to cp all your files up to S3 Bucket

aws s3 cp site/ s3://my-bucket-name/ --recursive --profile default

And that’s it, you may now visit your domain and you should have your cloudfront serving your assets from s3.

Thanks for reading, hope you find it helpful somehow, and if it helped you please share.

André Ilhicas dos Santos

Devops Padawan, curious about systems automation, learning new languages, paradigms tools each day.

ilhicas ilhicas