Hosting and deploying static websites using AWS S3

Hosting and deploying static websites using AWS S3

by Nuno -

With the advent of Single Page Applications, nowadays it is common to see web applications written in a JS framework (Angular, React, etc.) and to consume one or more APIs. This is a deviation from traditional MVC web frameworks like Ruby on Rails or Django. With this approach, the server app deals mostly with the M and the C parts: persistence of data, access to resources, etc. while the client app unfolds into another MVC structure.

This separation allows us to deal with hosting and deployments in a different way - we no longer have the server responding with dynamic HTML pages, instead our JS application resorts to JSON responses from which it builds a state and generates HTML via templates. All of this code - JS, HTML, and even stylesheets - are, naturally, static and can be hosted and served by a static web server.

For this blog post, we’ll be using AWS S3 and Cloudfront since they fit in very well on this scenario and are common tools among Web Developers. They do require a bit of configuration (Heroku spoiled us too much) but once that’s done, deployments can be automated into a simple script.

Hosting

Create the S3 bucket and name it exactly as the intended endpoint domain (eg: app.domain.com). Set the following Properties on the bucket:

  • Permissions: Grantee Everyone list
  • Static website hosting: Enable and set Index Document: index.html

At this point, you can point your DNS to the bucket endpoint. The URL is under “static website hosting”.

Cloudfront

This is optional, but AWS Cloudfront will speed up the delivery of your site with good throughout and low latency on (almost) anywhere in the world. Cloudfrount can also deliver you assets gzip-compressed automatically if a browser asks for it (via the Accept-Encoding:gzip header).

To configure an AWS Cloudfront distribution for S3, make sure you set these options:

  • Select the bucket as the origin
  • Allow HTTP Methods: GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
  • Set the proper CNAME
  • If you have an SSL certificate, specify it and set to redirect HTTP to HTTPS
  • Set root object to index.html
  • Compress Objects Automatically: Yes

Deployments

Make sure you have AWS CLI installed and configured with the proper AWS credentials, or load them from the deployment script, like this one:

#!/bin/bash
# load AWS credentials from this file - optional
source .env.aws

# build your app, for example with:
npm install && bower install || exit 1
grunt build || exit 1

# deploy files on ./dist to bucket
bucket="s3://app.domain.com"
region="us-west-1"
flags="--region $region --acl public-read --delete"
aws s3 sync dist $bucket $flags --cache-control "public, no-cache, max-age=43200"

This is an example script, you should adapt it to your project. For more options read the man pages for aws s3. (For example to set metadata, etc.) The flags used here are:

  • --acl public-read: sets the uploaded files as public readable
  • --delete: delete previous existing files with the same key (path + name). Without this flag, your old assets will lurk in the bucket, not a big deal but I like a tidy home :)
  • --cache-control "public, no-cache, max-age=43200": the only thing worth mentioning here is no-cache. This causes browsers to check if a newer version of the files exists. Since S3 gives us e-tags automatically, the server will check those first and reply with 304 if it matches, else just sends the full file.

Multiple Environments

If you have multiple environments (production/staging/etc.), you can easily enhance this script to ease your life:

# get current branch from arguments or git
if [ $1 ]
then
  branch=$1
else
  branch=`git symbolic-ref --short HEAD`
fi
echo && echo "Detected branch: $branch"

# set bucket and region based on current branch
case $branch in
  master  ) bucket="s3://app.domain.com"; region="us-west-1";;
  staging ) bucket="s3://staging.domain.com"; region="us-east-2";;
  * )
    # prompt bucket to deploy to
    echo "Unkown branch, please select bucket to deploy to:"
    echo "1 - app.domain.com"
    echo "2 - staging.domain.com"
    read bucket
    case $bucket in
      1 ) bucket="s3://app.domain.com"; region="us-west-1";;
      2 ) bucket="s3://staging.domain.com"; region="us-east-2";;
    esac
    ;;
esac

# ...

As always, this is just an example of where and how to deploy static websites. I’m sure there are plenty of other options. Personally, I find this process quick and simple, yet very efficient and cheap.


The downfalls of this approach, in my opinion, are mostly CI integration, because it requires AWS CLI in order to deploy to S3 and it’s not simple to revert a deployment (each deployment overrides previous files, although we can improve the deployment script by copying old files into a /releases directory, and copy back from it when rolling back, etc., at this point we’re more focused on writing solid Bash scripts instead of your application…)

If you’re handling lots of static sites, perhaps you should consider s3_website. It does require some extra configuration but at the end is quite powerful and popular!

Did you find this guide useful? How do you deploy your static sites? What tools do you find most useful? I’d love to know about it, so please tweet @Whitesmithco ;)

#the-forge

Nuno

More posts

Share This Post

The Non-Technical Founders survival guide

How to spot, avoid & recover from 7 start-up scuppering traps.

The Non-Technical Founders survival guide: How to spot, avoid & recover from 7 start-up scuppering traps.

Download for free