Back to Home

Host your Hugo blog for free with AWS Free Tier and Kamal

For many years, I hosted my Hugo blog on GitHub Pages. It’s a terrific free hosting provider for static websites (like blogs) and an especially great option for developers. However, as I began to write more frequently, I became a bit weary of its limitations like having to wait for atleast 5 minutes (realistically 10 - 20 minutes) for changes to become live or having to spend a surprisingly high amount of time to solve problems specific to GitHub Pages or GitHub Actions.

As I was growing weary of proprietary hosting providers, I discovered AWS’ Free Tier through an unrelated conversation I was having with a colleague on Slack. Once I discovered AWS’ Free Tier, moving my blog to a free EC2 VM was a no-brainer decision. Hosting on a Linux VM gives me much more control over hosting and sidesteps weird problems caused by technological/product/pricing limitations of proprietary hosting platforms.

Thanks to fantastic modern tooling like Basecamp’s Kamal (https://kamal-deploy.org), dockerizing and hosting my Hugo blog on an EC2 VM was fairly straightforward.

If you’re considering hosting your Hugo blog (or any static website) on a Linux VM, here’s how you can easily do using Kamal.

First, create a Dockerfile for your Hugo blog. Here’s a Dockerfile that you can use as a starting point

FROM golang:1.22-alpine AS blog

RUN apk update

RUN apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community hugo

WORKDIR /opt/blog

COPY . /opt/blog

EXPOSE 1313

CMD hugo server --watch --buildDrafts --bind 0.0.0.0

And here’s my blog’s Dockerfile

https://github.com/nisanthchunduru/nisanthchunduru.github.io/blob/master/Dockerfile

My Dockerfile is a tad lengthier as I use Tailwind CSS to style my blog and that meant that I had to add.

After creating the Dockerfile, install Kamal. Kamal is written in Ruby. If you’ve have Ruby installed on your machine, run

gem install kamal

If you don’t have Ruby installed on your machine, just follow the installation instructions in Kamal’s docs https://kamal-deploy.org/docs/installation/

Once you’ve Kamal installed, create its config files

kamal init

The init command should create the config/deploy.yml kamal config file

Now, create an AWS Account https://aws.amazon.com and start a t2.micro or a t3.micro EC2 instance (whichever’s available) as recommended on their Free Tier pricing page https://aws.amazon.com/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free Tier Types=*all&awsf.Free Tier Categories=*all

Modify config/deploy.yml so Kamal can deploy your blog. Replace yourdockerhubusername with your Docker Hub username and 1.2.3.4 with your EC2 VM’s IP address

# Name of your application. Used to uniquely configure containers.
service: blog

# Name of the container image.
image: yourdockerhubusername/blog

# Deploy to these servers.
servers:
  web:
    hosts:
      - 1.2.3.4

# Credentials for your image host.
registry:
  # Specify the registry server, if you're not using Docker Hub
  # server: registry.digitalocean.com / ghcr.io / ...

  username: yourdockerhubusername

  # Always use an access token rather than real password when possible.
  password:
    - KAMAL_REGISTRY_PASSWORD

healthcheck:
  path: /
  port: 1313

Kamal, by default, build and stores Docker images in DockerHub. To store Docker images in DockerHub, it requires a DockerHub access token. Visit DockerHub’s “Account Settings / Security” section https://hub.docker.com/settings/security and create an access token.

Now, run Kamal’s setup command to setup your Ubuntu VM and deploy your blog there

KAMAL_REGISTRY_PASSWORD=YOUR_DOCKERHUB_ACCESS_TOKEN kamal setup

Once Kamal deploys your blog, your blog will be accessible at your EC2’s VM public IP address.

DNS

Create a DNS A record in your DNS provider’s dashboard that points your blog’s domain to your EC2’s VM public IP address.

HTTPS

If you have purchased your blog’s domain from Cloudflare (like i did), turn on Cloudflare’s “Proxy” feature for the DNS A record you created above. That’ll cause Cloudflare to automatically obtain a SSL certificate and encrypt all traffic between visitors and Cloudflare (but not between Cloudflare and the EC2 VM which IMO is an acceptable compromise for low traffic personal blogs).

If Cloudflare is not your DNS provider or if you’d like traffic between Cloudflare and your EC2 VM to be encrypted, fret not. It’s still feasible to configure Trafik (a reverse proxy that Kamal installs) to automatically obtain a free SSL certificate from LetsEncrypt. I’ll add the steps for doing that shortly.

Built with Hugo & Notion. Source code is available at GitHub.