When you have Jekyll-built static websites and want to easily host them, you can do it on Amazon S3. We outline the exact process + all the code necessary.

Jekyll is an excellent tool for creating static sites (from plaintext, markdown, and other basic styles) that can be easily hosted on Amazon S3, GitHub pages, Heroku or any other provider that can serve HTML files.
In this article we will show you how to deploy an existing Jekyll-based website to S3 with CDN for improving the site’s performance, using CloudFront.

CloudFront for serving content over HTTPS

Serving static websites directly from S3 or GitHub pages works fine as long as you don’t need to serve it over HTTPS - neither allows custom SSL certificates for HTTPS. As far as we know, putting a load balancer, reverse-proxy or CDN, such as CloudFront, in front is the only solution (but if you know more do not hesitate to mention them in the comments!).

So with that, let’s dive into our solution. We will start by creating a new Content Distribution on CloudFront. Visit the AWS CloudFront Management Console, click Create Distribution, and select Web as the Delivery Method. Fill the Origin Settings section as outlined in this screenshot:

images/blog/2019-02-08-hosting-a-jekyll-website-on-s3-with-ssl/image1.png

In the field where we put your_domain_or_bucket type the S3 endpoint that can be found in the Hosting Website section (ie: your-bucket.s3-website.eu-central-1.amazonaws.com). This allows you to respect the S3 redirect rules when accessing the website through CloudFront.
Other sections can be left with default settings for now.

In the Distribution Settings section specify the SSL certificate you want to use:

images/blog/2019-02-08-hosting-a-jekyll-website-on-s3-with-ssl/image2.png

If you don’t have a certificate added yet, then go to the Certificate Manager service on AWS and upload one there. It’s better to do it this way than import it directly in the Distribution Settings, because it makes managing the certificate easier (uploading updated versions, etc.).

Versioning static files

Before we start using CDN for serving content, we also need to make sure we have correct caching headers - so that unmodified files can be loaded from the browser’s cache. Despite this, we will need to be able to deliver new versions whenever they are updated (even when the cache headers are still valid). To do this, we will fingerprint file names - so the browser won’t find the new file in its cache, triggering a request to fetch it from our CDN.

For managing assets we will use the jekyll-assets gem. Add it to your Gemfile, run bundle install and update the _config.yml as below:

gems:
  - jekyll-assets
 
assets:
  autoprefixer:
    browsers:
      - "last 2 versions"
      - "IE > 9"
  prefix: '/assets'
  assets:
    - "main.css"
    - "*.js"
    - "*.png"
    - "*.jpg"
    - "*.eot"
    - "*.ttf"
    - "*.woff"
  sources:
    - _assets

Migrating stylesheets

We also need to move all the stylesheet files from the _sass directory to assets/css and then update main.scss to be our entry point for importing other files:

@import "variables";
@import "global";

In the above example we assume you have the following structure in your _assets/css directory:

  • _global.scss
  • _variables.scss
  • main.scss

Remember to update your path to the assets in your templates and stylesheets:

  • Replace <img src="path/to/image.jpg" alt="some alt" /> with {% img 'path/to/image.jpg' alt:'some alt' %}
  • Replace <script src="path/to/script.js" /> with {% js 'path/to/script' %}
  • Replace <link rel="styleshee" href="path/to/stylesheet.css" /> with %{ css main.css %}
  • If you reference any files (fonts, images) in your stylesheets replace url('path/to/file') with url(asset_path('path/to/file'))

For more info visit the jekyll-assets page.

Migrating javascripts

We also need to move our scripts to assets/scripts folder and create a main.js file to reference our other files:

//= require jquery
//= require bootstrap

And include that main.js file in the template:

{% js main.js %}

Now when you build your website with the JEKYLL_ENV set to production the gem will concatenate, compress, and fingerprint your asset files automatically.

Deploying to S3

In order to deploy our static website to s3 we use the s3_website gem. It allows us not only to push the files to the bucket, but also to create invalidation in our distribution for our *.html files. HTMLs can’t be fingerprinted because we don’t want to change the links every time we deploy a new version.

Add the s3_website gem to your Bundle file and run the bundle install command. You’ll also need to have Java installed because the gem is using Scala under the hood.

Now create a basic config file in the project root:

s3_website.yml
s3_id: AWS_ACCESS_KEY
s3_secret: AWS_SECRET_KEY
s3_bucket: your-bucket-name

index_document: index.html
error_document: error.html

max_age: 3600

gzip: **true**

s3_endpoint: eu-central-1

cache_control:
"assets/*": **public**, max-age=3600
"*": no-cache, no-store, max-age=0 must-revalidate

cloudfront_distribution_id: YOUR_CLOUDFRONT_DISTRIBUTION_ID

cloudfront_distribution_config:
default_cache_behavior:
min_ttl: <%= 60 * 60 * 24 %>
aliases:
quantity: 1
items:
- www.your-website.com

cloudfront_wildcard_invalidation: **false**

We disable caching (no-cache, no-store, max-age=0 must-revalidate) for all files except assets (those will be HTML files anyway unless you use a custom setup) .

Let’s apply those settings to our CloudFront distribution to check if everything is configured correctly:

bundle exec s3_website cfg apply

If you get something similar to the message below, it means the website is ready to be deployed:

Applying the configurations in s3_website.yml on the AWS services ...
Bucket your-bucket now functions as a website
No redirects to configure for your-bucket bucket
Bucket your-bucket is now readable to the whole world
Detected an existing CloudFront distribution (id 123456789) ...
  Applied custom distribution settings:
    :caller_reference: '123456789'
    ...

To deploy the website to S3, simply use the push command:

bundle exec s3_website push

If you would like to check which files are going to be sent first, then you can pass the --dry-run flag to the above command.

This will create invalidation on CDN. To check its status, go to CloudFront, select your distribution and visit the Invalidations tab. Usually it takes a few minutes before the HTML file is invalidated in CDN and a new version from your S3 bucket is fetched.

For more deployment configurations, organizing your website infrastructure, or website development in general, you can ask the experts at iRonin for assistance. Our team of highly skilled specialists have deep experience and are available to take a closer look at all your web development needs.