Hosting Serverless Hugo on AWS

This is a work in progress, and I will probably update this post anytime soon, or will create a new one(s), to elaborate the topic(s).

Note: This post is made for somebody with experience, but I tried to keep it as simple as possible so somebody without much technical knowledge would understand. That’s why I elaborated some terminology. Probably this is to be updated soon to be understood more clearly to people who are new to this.


Making a decision of which platform to use for blogging, since you’ll probably stick with it for a long time, is an intimidating task. However, my suggestion is that if you want to write a blog, go with as simple setup as possible.

I decided to host this blog on AWS mostly because of the free tear and because I was a little familiar with it from work. The way I host is 100% serverless. I don’t care about running a server, if it’s up or down, security of it and similar. I just know that the services listed below will do the work for me.

This is the list of AWS services (and some other programs) I’m using right now to host, build the site automatically, and release it to the public:

  1. GitHub - I’m using GitHub private repository for the project, that’s where I write down ideas, the blog itself, and I have a few bash scripts to help me work a bit easier on the blog.
  2. JetBrains PyCharm - This is IDE (Integrated Development Enviroment) I’m using for writing. It’s primarily used by Python developers to write code, but I’m using it to write Markdown - a file format that Hugo parses and makes a static website out of it. I chose PyCharm for few reasons, mostly being familiar with JetBrains products (I’ve held two seminars about IntelliJ IDEA two years ago), having integration with GitHub and a nice split view of markdown file I’m writing and a preview. Also, since it’s lightweight, it loads faster than other IDEs I use, which is a plus for writing Markdown and using GitHub.
  3. AWS Route 53 - This is DNS (Doman Name System), a service where you register your domain, I’ve registered my domain (milosgarunovic.com) with it.
  4. AWS Certificate Manager - This service provides you an SSL certificate, used for serving pages as HTTPS. Using CloudFront (up next), you get free certificate for the domain.
  5. AWS CloudFront - Used for content delivery. Everything you see on this site is served with CloudFront. It get’s the content from AWS S3 (up next).
  6. AWS S3 - S3 stands for Simple Storage Solution - as name suggests, it’s a storage service, and that’s where all the files of this website are stored.
  7. AWS CodeBuild - It’s a bit of extra work to have a script that will do the thing CodeBuild was made to do. CodeBuild serves as Continues Integration/Delivery service, that get’s repository information from GitHub, installs necessary dependencies (in my case that’s just Hugo), builds the project and serves it to S3. This way, I can focus on writing, and know that everything will be up and runing in a minute or so after I push my new post to GitHub.
  8. AWS CloudWatch - Used for viewing logs of CodeBuild. This is mostly for debugging - figuring out why the build failed.

And this is the list of AWS Services I’m planning to use in near future to further extend my website and administration capability:

  1. AWS SNS - SNS stands for Simple Notification Service. Planing to include this into my build process, along with CodeBuild and CloudWatch, to send me an email that the site has been built, or if any error occurred. This way I don’t need to log in to AWS Console to see every change.
  2. AWS SES - Once I build more posts, in about a year, I will add subscription mechanism to this blog. SES - Simple Email Service should do the job of sending emails to the subscribers. Possibly I’ll use this with Sendy.

One important thing I’ve gained with this tech stack is that I don’t have to always be at my machine to write and release the blog. I can write from my mobile, other devices and similar. All I need is access to internet. So now I’m not tied to one machine.

That’s a lot of services to do a simple thing like hosting a website

Well, yes, that’s a lot of services, and it may be overwhelming. But trust me that most of them are simple services that you use for specific tasks. It’s not that hard to get the service to run as you wish, and you don’t have to know everything about it. I know just a little bit about every one of them, just enough to do the job that I need.

Setting up the services

I won’t go into details, but setting up the services was a lot of trial and error. I’ve probably spent more than fifteen hours setting things up. Most of which was spent trying to understand the terminology, some of them fixing bugs and debugging.

For the record, Route 53, Certificate Manager and CloudWatch were the easiest to set up. Each took up to 10 minutes to figure out and set up (and it’s my first time dealing with any of them). I think the most problems came from AWS S3 and CloudFront for beginning, and once I set up the CI/CD, CodeBuild caused a lot of trouble (for the record, it takes about 15-30 seconds to build this site, and I’ve spent 70 minutes of building to get it right).

The flow

So how it’s all connected?

I write posts in PyCharm, and once I have some part complete, or the whole post, I push it to development branch on GitHub. There are two branches on GitHub - master and development. Both serve mostly the same purpose. The development branch serves a site as well, but I don’t have a domain name attached to it, and it’s not using CloudFront. You can just use S3 bucket domain name. Next, once pushed on GitHub, it will send a webhook to AWS CodeBuild. CodeBuild then installs hugo, runs the creation and copies the files to S3. S3 is set up to serve static web site, so it has a domain name on it’s own. That domain name is registered as CloudFronts Origin (this caused a bug at first, since I took just the bucket name (this is the default CloudFront offers), not the domain name of the bucket. You can read more about the issue here). CloudFront also has CNAME set to my domain name milosgarunovic.com, and Custom SSL Certificate - issued by AWS Certificate Manager.

CloudWatch comes into play for CodeBuild, so I can see the output of the build, why it failed, was it successful, and I used it for outputting the filesystem, since I had issues with Git Submodules. I’m using CloudWatch as a logging service, it just outputs the CodeBuild logs.

The issue with Git Submodules and CodeBuild fix - CodeBuild asks you if you want to use Git Submodules, and you check the box, the build fails. The submodules are required for the theme of the blog - at the time of writing, I’m using hyde-hyde. For some reason I can’t detect the issue even with logging, since it fails at the beginning (probably when pulling the project from GitHub). To resolve the issue, I used a command to get the submodule downloaded, and unchecked the checkbox from CodeBuild. Tho this part will have to be updated soon, since I want fixed commit to show up. For those of you wondering, here’s the buildspec.yml that CodeBuild uses:

version: 0.2

phases:
  install:
    commands:
      # - apt-get update
      - wget https://github.com/gohugoio/hugo/releases/download/v0.53/hugo_0.53_Linux-64bit.deb
      - dpkg -i hugo_0.53_Linux-64bit.deb
      # - apt-get install tree #for debugging
  pre_build:
    commands:
      - git submodule update --init --recursive
      # - tree -a --dirsfirst themes #for debugging
  build:
    commands:
      - hugo --verbose
#  post_build:
#    commands:
      # - tree -a --dirsfirst public #for debugging
artifacts:
  files:
    - '**/*'
    # - location
  #name: $(date +%Y-%m-%d)
  #discard-paths: yes
  base-directory: public
#cache:
  #paths:
    # - paths

Basically, it says “install hugo, get Git Submodule, run the hugo command to build the project, and put everything from public directory to S3”. The S3 part is defined as Artifact in CodeBuild, so it’s not defined in the buildspec.yml. I just define what will be the output (in this case everything from public directory), and the artifact that consumes the output can be changed.

Once I see that everything is on the development domain, I make few updates if necessary (fixing typos and similar), and I then merge content to master branch on GitHub. This triggers another webhook that builds my project (again, but I think it’s using cache, so it builds faster), and it updates the actual milosgarunovic.com website.

Just as a side note, GitHub webhooks aren’t related to branches. They will be triggered every single time an update to GitHub repository is created. So you have to filter which branch you want to use, for that you can head to CodeBuild, your build project, edit source, and find webhooks. There, you click “Start a build under these conditions” and put refs/heads/development or refs/heads/master in HEAD_REF - optional field. That will filter only the given branch and make a build for that branch. This is how I separate development and production phase.

Pricing

This is only the first month I’m hosting, but I’ll try to update this in few months from writing to make some estimates. Keep in mind that I’m still in AWS Free Tier. In February, I’ve paid $12 for domain registration, $0.50 for hosted zone, and 20% VAT on that, total of $15. In March, I’m paying, again $0.50 for hosted zone, $0.21 for AWS Lightsail instance (a web server I’ve used for learning, it’s usually $3.5 monthly), and 20% VAT on that, total of $0.85.

So for now, I’m serving this site for less then a dollar, while having everything automated.

A word about CodePipeline

I first wanted to make it work with AWS CodePipeline. But I figured out I don’t need it (maybe for now, since I still don’t have any notification system). Everything I need for now is done through CodeBuild, which is just one part of CodePipeline. CodePipeline would just duplicate the work for no reason, so I figured I just won’t use it. For this scenario, it isn’t necessary.

What’s next?

I didn’t want to go into too much details in this post, but just give overview of the tech stack I use for writing and hosting. If you have some experience, you can figure out how to do this yourself. But I’ll write in next few posts what steps I took to build everything, in more details.