7 min read

Deploy a Static Site or Single Page App on Amazon S3 with a custom domain and free SSL via GitLab CI


AWS is an incredible resource for web developers, but obvious and easy to use it is not. Seriously, if you have ever got frustrated that you couldn't get something to work on AWS, or you have read the documentation and felt utterly lost, YOU ARE NOT ALONE.

This is the first in a series of short guides on how to launch your site on AWS with a minimum amount of fuss.

What we're going to do

  1. Setup S3 buckets for file storage
  2. Use AWS Certificate Manager to generate free SSL certificates
  3. Configure AWS CloudFront to connect your custom domain to S3
  4. Write a simple Gitlab CI pipeline for automated deployment

Setup S3 buckets for file storage

Head across to the AWS website console and locate the S3 section. Click the Create Bucket button and create 2 new buckets. For example:

  • domain.com
  • www.domain.com

We'll only be storing files in the first bucket, the second one exists only to redirect to your main domain. Click on your main bucket and find the Properties tab. Click on the Static Website Hosting box and tick Use this bucket to host a website. This will give you a new url for your bucket like http://domain.com.s3-website-us-east-1.amazonaws.com.

If you're using a javascript framework like React or Vue then you will most likely want to send all requests to the index.html page where it will be routed, so put index.html in both the Index document and Error document boxes. Save those changes and head over to the www.domain.com bucket you also created.

Enable static hosting on your www.domain.com bucket instead of enabling website hosting, select the Redirect requests option. Enter your main bucket name in the Target bucket or domain box, and type https in the Protocol box.

Finally you should make your main bucket publically readable. You can find out how to that in my other article.

Use AWS Certificate Manager to generate free SSL certificates

Remember when SSL certificates used to cost money?? Thankfully those days are over. Everything you put online should be secure now we have services like Let's Encrypt. In AWS land we can create free certificates using Certificate Manager so let's get started!

In the Certificate Manager part of the AWS console website select Request a certificate and when prompted to add a domain name, add domain.com and a wildcard version *.domain.com. (Obviously, you should replace this domain name for your chosen domain.) This will save time later if you add any subdomains as it will cover all of them.

You will need to validate your certificate request. You can do this via email, or via DNS. If you are using Route53 to manage your domain, and if you are running your site on AWS I highly recommend you do, you can automatically validate the request on the next screen by adding the validation CNAME records with a single click. Nice!

It should take approx 5 minutes to validate the certificate once you have followed the steps. The validation status of each will switch from Pending validation to Issued.

Configure AWS CloudFront to connect your custom domain to S3

AWS CloudFront is a beast. There's so many potential options and settings. To be brutally honest it's annoying we even need to use it for such a simple setup. However, there's no other way of using a custom SSL on an S3 bucket so we'll have to suck it up and dive in. Luckily we only need to configure a few things, and on the upside, we'll also get the benefit of a faster website.

Access the CloudFront part of the AWS website. We'll need to create two distrubutions, one for each of the S3 buckets we created earlier. Click Create distributions and click Get Started under the Web option.

The majority of the default settings can be left alone. You should only change the following:

  1. Origin Domain Name Don't select your bucket from the dropdown. Instead, paste in the URL you got when you switched on static web hosting. It should look something like domain.com.s3-website-us-east-1.amazonaws.com without the http.
  2. Allowed HTTP Methods Choose the Redirect HTTP to HTTPS option.
  3. Alternate Domain Names (CNAMEs) Enter the domain name, eg: domain.com or www.domain.com depending on which one of the two you're setting up. Remember you need to set up a separate CloudFront distribution for each.
  4. SSL Certificate Switch to the Custom SSL Certificate option and choose the *.domain.com wildcard SSL you created earlier for both distributions.

Once you have created both distributions you'll need to wait for them to deploy. It takes AGES. Be patient. Maybe fill your time by following me on Twitter? 😉

When the distributions have deployed successfully you'll need to point the A records in your DNS to them. In AWS Route53 select the hosted zone for the domain name you are using. Click Create Record Set and create an A record for your main domain and www subdomain using the Alias option (or update existing records if they exist already). From the edit record set panel Alias Target dropdown select the CloudFront distributions. It will look something like this:

  1. domain.com alias to dapxmo622ty2z.cloudfront.net
  2. www.domain.com alias to d165pcqgirq9g8.cloudfront.net

Write a simple Gitlab CI pipeline for automated deployment

Take a deep breath. We're almost there! The final step in this tutorial is to automate deployment from GitLab. There are a million other ways of getting your files across to your S3 bucket so it's far from a required step. It's just the setup I use so it may be useful to you if you also use GitLab CI Pipelines (which are fantastic, and free!).

First, we need to give GitLab programmatic access to the S3 and CloudFront parts of our AWS account. In the AWS console find the IAM section and click Users and then Create User. Give the user something like GitLabDeployments. Tick the Programmatic access checkbox and move to the next page. Select the Attach existing policies directly and use the search box to find the following two policies: AmazonS3FullAccess and CloudFrontFullAccess.

Skip the tags part as it's not needed here and save your user. You will be given an Access ID and Access Key. Make a note of them for later as you won't be able to access them again once you leave this screen.

Head back over to your IDE and the root directory of your project. Create a .gitlab-ci.yml file. Paste the following code into that file and save.

  - build
  - deploy

  BUCKET_NAME: domain.com #put your main bucket here

  stage: build
  image: node:latest
    key: ${CI_COMMIT_REF_SLUG}-npm
      - node_modules/
    - yarn install --progress=false
    - BUCKET_NAME=$BUCKET_NAME yarn run build #put your build script here
    expire_in: 1 hour
      - dist/ #this is the directory your site builds into
    - master

  stage: deploy
  image: python:latest
    - pip install awscli
    - aws s3 sync ./dist s3://$BUCKET_NAME #your build directory
    - aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths '/*'
    - master

This example is from this very blog so it only has stages setup on the master branch. On a production website, you can set up different stages for your staging and production branches of GIT. I'll blog more about what can be done with GitLab CI Pipelines in the future. The possibilities are endless!

This pipeline will build your site using the build script, in this case, Vue, generating a dist directory. It will then use the Python AWS tool to copy the files across to S3.

This pipeline requires a few environment variables to be set in GitLab. In your project in GitLab select the CI/CD option in the Settings dropdown in the sidebar. Expand the Environment Variables panel and create the following:

  • AWS_ACCESS_KEY_ID The id you created in AWS IAM earlier
  • AWS_SECRET_ACCESS_KEY The key you created in AWS IAM earlier
  • CLOUDFRONT_DISTRIBUTION_ID The ID of the CloudFront distribution you created earlier for the main site (not the www).

You're all done! 🎉

Now this is set up you can forget about it. Each time you make changes to your site and push to the master branch in GIT your CI pipeline will do the following automatically:

  1. Generate your site's files using the build process in Docker
  2. Copy the built files across to your S3 bucket
  3. Clear the CloudFront cache so you can see the latest version of your site