Cloud. Applications. UX.

A quest to un-complicate the hard stuff.

How to host Hugo static website generator on AWS Lambda

In this tutorial we will create a static website publishing platform hosted in the cloud using several AWS services. It will run static website generator Hugo on AWS Lambda, store files in AWS S3 and serve them from AWS CloudFront. You will learn what these services do and set them up with easy-to-follow step by step instructions and screenshots.

My website is built with Hugo and is hosted on AWS using the configuration described in this tutorial. I spent a few evenings configuring it but this tutorial will get you started in less than 30 minutes.

Disclosure

I work for AWS but this tutorial is product of my own tinkering. It was in no way sponsored or endorsed by AWS.

This tutorial uses content, code snippets and ideas from a number of open source projects. See Acknowledgements for details.

What are we creating?

Below is the architecture of Hugo running on AWS Lambda.

Hugo Lambda architecture

This is how it works:

  • Input Bucket contains Hugo files: content, themes, images, etc. When a file is uploaded or deleted, a Lambda function is triggered.
  • Lambda function is a piece of code (Hugo in this case) that runs on a Linux server in AWS cloud. You don’t manage any servers and pay only for the time the function runs in 100ms increments. When the Lambda function is triggered, it downloads files from the Input Bucket to /tmp folder in the Linux server, runs Hugo to generate the static website and copies the site’s HTML files to the Output Bucket.
  • Output Bucket contains your static website and is configured for serving web pages to the visitors of your site.
  • CloudFront Distribution is a Content Delivery Network (CDN) service that replicates your files across multiple locations worldwide, so that the visitors of your website load the site from the location that’s closest to them minimizing the load time.
  • Route 53 is a Domain Name Service (DNS) that “points” your visitors to the CloudFront Distribution when they type “yourwebsite.com” in the browser.
  • IAM, which stands for Identity and Access Management, is a service that helps secure your AWS resources.

In this tutorial we will configure the part surrounded by the dotted box. Configuration of CloudFront and Route 53 is covered by AWS Static Website Quick Start in great detail and rewriting it here would not be very productive. I will point you to the appropriate Quick Start steps at the end of the tutorial.

1) Create a sample Hugo site

Hugo is a popular static website generator and basic knowledge of Hugo is recommended for this tutorial. Follow this quick start guide to create your first Hugo site or download a sample Hugo website that I created for this tutorial.

Download link: Download sample Hugo website
md5 checksum: 5c2e248ddaaf36d361deb006782bf742

If you have an existing Hugo site, you can port it to AWS using this tutorial.

2) Login to AWS Console

AWS Console is the front-end into AWS located at console.aws.amazon.com. If you don’t have an AWS account, go to aws.amazon.com and create one.

This is the screen you should see after you’ve logged in.

AWS Console Home

3) Configure file storage in AWS S3

AWS Simple Storage Service (S3) is well… a storage service where you can store files. S3 is cheap, reliable and fast.

S3 offers free tier pricing for new customers, which should be enough to host a basic web site (e.g. a blog or a small business website) with reasonable amount of traffic at no cost for the first year. After free tier expires or if you go over free tier limits, a basic site will cost just a few cents per month.

S3 organizes files in buckets, which are essentially top level folders. To run a static website we will need to create three buckets:

  • Static website bucket where your website will be served from. Think of it as a webserver whose only capability is to serve static content.
  • WWW bucket that will simply redirect traffic from www.yourdomain.com to yourdomain.com
  • Input bucket where you will store all your Hugo files, such as content, images and theme

3.1) Create Static Website bucket

3.1.1) Click on S3 in the list of AWS services (under “Storage & Content Delivery”)

3.1.2) In S3 console, click Create Bucket

3.1.3) Enter your website’s domain name and click Create

S3 requires bucket names to be unique across S3. If you already created a bucket with your domain’s name, you will need to use it for this tutorial.

Select the region that’s likely to get you the best connection speed based on your location. However, keep in mind that Lambda is currently only available in US East (N. Virginia, US West (Oregon), EU (Ireland) and Asia Pacific (Tokyo).

Create S3 bucket

3.1.4) Configure static website hosting

AWS S3 can be used as a web server for static content. Unlike traditional webservers, you don’t have to manage any infrastructure. S3 just serves your files when they are requested and scales with traffic.

After the bucket has been created, follow these steps:

  1. Click on the bucket
  2. Click Properties
  3. Expand Static Website Hosting in the list of properties
  4. Select Enable website hosting
  5. Enter “index.html” in Index Document
  6. Enter “404.html” in Error Document
  7. Important: Copy the Endpoint URL that looks like yourwebsitedomain.com.s3-website-us-west-2.amazonaws.com. This will be the URL for your website until you connect your domain name to it.
  8. Click Save

Configure Static Website Hosting

3.1.5) Add bucket policy

You need allow S3 to serve your website to anyone on the Internet. This can be done by creating a security policy for the S3 bucket that will allow anyone on the Internet download files from your bucket.

While still in bucket’s properties, do the following:

1. Expand Permissions in the list of properties

2. Click Add bucket policy

Add bucket policy

3. Copy the following policy into the policy text box and change the highlighted part to your domain name

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::yourwebsitedomain.com/*"
            ]
        }
    ]
}

4. Click Save

Add bucket policy

3.2) Create WWW bucket

3.2.1) Create a bucket named “www.YOURDOMAIN.COM”

Create S3 Bucket for WWW Domain

3.2.2) Configure WWW bucket to redirect to non-WWW bucket

  1. Click on the bucket
  2. Click Properties
  3. Expand Static Website Hosting in the list of properties
  4. Select Redirect all requests to another host name
  5. Type your domain name in the Redirect all requests to text box

Redirect WWW

3.3) Create Input Bucket for storing Hugo files

3.3.1) Create a new bucket named “input.YOURDOMAIN.COM”

Create S3 Bucket for Hugo files and content

3.3.2) Upload Hugo files to the bucket

1. Click on your bucket and click Upload

2. Select files for your Hugo site

If you downloaded the sample site, unzip it and upload all folders and files to the Input bucket.

Note that it is faster to open a Finder (or Windows Explorer) folder and drag and drop Hugo files to the browser rather than adding them via Add Files.

Upload files to input bucket

3. Click Start Upload and wait until the upload is finished

This concludes configuring storage for the website!

4) Configure security settings for Lambda

In order to run Hugo on AWS Lambda, we need to allow it to access the S3 buckets we’ve just created. In AWS world this is done through roles and policies in Identity and Access Management (IAM) service. IAM role is a set of policies, each allowing or disallowing particular actions with your AWS resources. When a user or another service assumes a role, they will only be able to access resources specified in the policies attached to the role.

It is a good idea to only allow actions that are necessary for the code to run and nothing more. For Hugo to run, we will need to allow it a read-only access to the Input bucket and access to manipulate files on the Static Website bucket. This will enable Hugo running inside Lambda environment to read the input files, generate the website and put it into the bucket that serves the website.

4.1) Go to Identity and Access Management (IAM)

  1. Click on “Services” in the navigation
  2. Click “Security & Identity”
  3. Click “IAM”

Navigate to IAM

4.2) In the left hand menu of IAM, click Roles

IAM Sidebar

4.3) Click Create New Role

Create IAM Role

4.4) Give your role a meaningful name and click Next Step

Create IAM Role

4.5) Select AWS Lambda in the Role Type and click Next Step

Create IAM Role

4.6) Select AWSLambdaBasicExecutionRole and click Next Step

Hint: type “exec” in the filter.

Create IAM Role

4.7) In the Review page, click Create Role

4.8) Select the role you’ve just created

4.9) Click Inline Policies to expand it and click click here to create a new policy

Create IAM Role

4.10) Select Custom Policy and click Select

Create IAM Role

4.11) Add custom policy

  1. Give policy a meaningful name such as AccessToHugoS3Buckets
  2. Copy the code below and paste it to the text box
  3. Replace highlighted parts with your domain name
  4. Click Apply Policy
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListAllMyBuckets"
            ],
            "Resource": [
                "arn:aws:s3:::*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::input.yourwebsitedomain.com"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::input.yourwebsitedomain.com/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::yourwebsitedomain.com"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:DeleteObject",
                "s3:GetObject",
                "s3:PutObject",
                "s3:GetObjectAcl",
                "s3:PutObjectAcl"
            ],
            "Resource": [
                "arn:aws:s3:::yourwebsitedomain.com/*"
            ]
        }
    ]
}

Create IAM Role

We are done with security settings. Now let’s have fun with Lambda.

5) Create a Lambda function

5.1) Create a function

5.1.1) Download the Lambda function code

I’ve packaged all the necessary files to run Hugo on Lambda in a single zip file.

Download link: Download Lambda function for Hugo
md5 checksum: 030c8b8432d069c734d968f3b150ed73

The package includes:

  • Hugo 0.15 binary for Linux 64-bit
  • RunHugo.js function from the hugo-lambda package (with a minor update in the code)
  • Node.js packages spawn and util necessary to run the function

5.1.2) Click on Services in the navigation and select Lambda under All AWS Services -> Compute

Service list

5.1.3) In Lambda console, click Get Started Now

Lambda Console

5.1.4) In the New function screen, click Skip

5.1.5) Configure parameters for the function

  • Name: Give it a meaningful name
  • Runtime: Node.js
  • Code entry type: Upload a .ZIP file
  • Upload: Upload the Lambda function file you downloaded in step 5.1.1.
  • Handler: RunHugo.handler
  • Role: Select the IAM role you created in the previous step
  • Memory: Leave at default (128Mb)
  • Timeout: Set to 30 seconds

After you’ve entered parameters, click Next

Lambda Create Function

5.1.6) Click Create function in the review screen

Lambda Review

5.2) Test the function

5.2.1) In Actions menu select Configure test event

Lambda Configure Test Event

5.2.2) Copy the following code into the text box and replace highlighted parts with your domain name and region.

If you selected Oregon when you created the S3 bucket, leave the region as is (us-west-2). If you selected a different region, replace us-west-2 with the appropriate region code from this table.

{
  "Records": [
    {
      "s3": {
        "object": {
          "eTag": "50ed8c18234b65e3baf1417eac1bb03f",
          "size": 307,
          "key": "content/jobs/fossbox.md"
        },
        "bucket": {
          "arn": "arn:aws:s3:::input.yourwebsitedomain.com",
          "name": "input.yourwebsitedomain.com"
        },
        "s3SchemaVersion": "1.0"
      },
      "eventVersion": "2.0",
      "eventSource": "aws:s3",
      "awsRegion": "us-west-2",
      "eventTime": "2015-02-08T22:50:04.028Z",
      "eventName": "ObjectCreated:Put"
    }
  ]
}

5.2.3) Click Save and test

Lambda test event code

5.2.4) Check the execution status

Scroll down the page and you’ll see a test function executing. Once it completes, you should see Execution result: succeeded along with a green check icon.

Lambda test success

5.3) Set up a trigger

Lambda functions sit idle and wait to be executed. We need to configure our Lambda function to be triggered when a file is added or removed from the Input bucket.

5.3.1) Go to Event sources tab and click Add event source

Lambda event sources

5.3.2) Add event source for new objects

  • Event source type: S3
  • Bucket: Select your Input bucket from the list
  • Event type: Object Created (All)

Click Submit

Lambda create trigger

5.3.3) Add event source for removed objects

  • Event source type: S3
  • Bucket: Select your Input bucket from the list
  • Event type: Object Removed (All)

Click Submit

Lambda remove trigger

Now everything is configured, it’s time to see it working!

6) See Hugo on Lambda in action!

6.1) Create a sample content file

Create a file named test.md and paste the following content in the file.

+++
Description = ""
date = "2015-12-12T21:47:31-08:00"
menu = "main"
title = "Hugo on Lambda test page"

+++

Congratulations, you've set up Hugo on AWS Lambda!

6.2) Upload this file to your input bucket

  1. Go to Services -> S3
  2. Click on your Input bucket
  3. Go to content/ folder
  4. Click Actions -> Upload
  5. Upload test.md to the content/ folder

6.3) Go to the static website URL you noted in step 3.1.4.

You should see a Hugo site and the test content.

Test site

Next steps

Now you should have a working static website that’s generated by Hugo running on AWS Lambda. A logical next step would be to connect your website to a domain name and speed up the site using CloudFront Content Delivery Network service.

AWS has published a Getting Started guide on static websites and you can simply follow steps in that guide:

Acknowledgements

  • Hugo is a static website generator that simplifies creation of simple websites. My website is built using Hugo.
  • Hugo-lambda is an open-source project that enables Hugo to run on Lambda. I borrowed the Lambda function and included it in my package (license included). With Hugo-Lambda you can get your Hugo site on AWS in a matter of minutes since it does all the heavy lifting for you.
  • Kappa is a command line tool that simplifies deployment of Lambda functions.

Ilya Bezdelev

Senior Product Manager @ AWS. Developer. Designer. Guitarist.

Tinkering with web apps since 1998.
All opinions are my own.