After getting fed-up with WordPress, I wanted to migrate onto some sort of static site generation. And since you're reading these words right now, it looks like that migration succeeded!
I ended up choosing Hakyll and Amazon S3 as my build and hosting solutions of choice, and I'm pretty happy with both. (Although some of the other elements of the AWS ecosystem, not so much. More on that later.) This post is more as a reference for myself, in case I need to tear things down or things go haywire.
Why Hakyll?
Honestly, because I felt like it. It's Haskell, it seems interesting, so why not. Yeah, yeah, I know it's not really a great reason.
The biggest advantage is that it comes with Pandoc support right out of the box, which lets me be very flexible in how I write my posts/content. In particular, being able to compile Literate Haskell files without needing to jump through any hoops is massive.
Hakyll is also very much "configuration over convention," and the entire site specification is just a normal Haskell program. While the amount of flexibility and customization is a little overkill for what I have right now, it's comforting to know that I'll never have to twist the site out of shape to get it to do something unusual.
Cool, so how is this site working at all?
The 10,000-meter view of this blog is:
- I write blog posts in
$EDITOR
and keep track of them with Git - New posts get pushed up to AWS CodeCommit (aka. AWS' heavily gimped version of GitHub)
- I trigger a build of the site using AWS CodeBuild, using a custom-built Docker image with Hakyll installed
- Once finished, CodeBuild uploads the site assets to S3
- AWS CloudFront and Route 53 handle the arcane magic of connecting the URL in your browser to S3
Most of this should be pretty easy to set up, although there are a few subtleties:
The Docker image I use is actually built using a NixOS base image, because I like making things even harder for myself. You probably don't need to do this, and can just build your image off of the Debian-based Haskell images.
Regardless of what base image you use, you'll likely want to add a
~/.stack/global-project/stack.yaml
to your image with a resolver specified. Use the same resolver as your actual Hakyll project. For example, I have the following lines in myDockerfile
:ADD ./stack.yaml /root/.stack/global-project/stack.yaml RUN stack build --system-ghc hakyll
This way, I don't have to spend time compiling Hakyll itself when building the site in CI.
If you do use a NixOS base, you'll need to:
Add a new CA Certificate so that you can make HTTP requests to GitHub.
# Dockerfile ADD ./digicert.crt digicert.crt RUN apk update RUN apk add ca-certificates RUN update-ca-certificates digicert.crt
Configure Stack to not do pure Nix builds, because otherwise locale information won't get passed into the build environment, and Hakyll won't be able to render Unicode characters.
# Dockerfile ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8
# ~/.stack/config.yaml, within your image nix: enable: true pure: false
You either need to specially configure CloudFront with access to S3 (which I didn't do) or mark the S3 bucket as public access. Unfortunately, there's no way to do this properly without creating an actual JSON policy document; otherwise, everything you upload will still be private, even if it looks like your bucket is public. Thanks, AWS. Here's the one I'm using:
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowPublicRead", "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::${BUCKET_NAME}/${FOLDER}/*" } ] }
Don't forget the wildcard on the resource name.
When you create your SSL certificate, don't forget to request both
*.domainname.com
anddomainname.com
!CloudFront caches pages for around a day, so don't expect instant updates. Use the S3 URLs directly if you need to see the results immediately.
If you have the choice, don't use CodeCommit and CodeBuild. They're just crappier versions of better options elsewhere, like GitLab. Use those better options instead. For instance, you may wonder why I don't use CodePipeline to automate the deploy process. Well, it doesn't seem like there's a way to get CodePipeline to not override where the site build artifacts go! Lovely.
So yeah, there you have it. A (kind of) working build and deployment system for a Hakyll-based site! Seriously, just go use GitLab or GitHub Pages.
Before you close that tab...
Want to write practical, production-ready Haskell? Tired of broken libraries, barebones documentation, and endless type-theory papers only a postdoc could understand? I want to help. Subscribe below and you'll get useful techniques for writing real, useful programs straight in your inbox.
Absolutely no spam, ever. I respect your email privacy. Unsubscribe anytime.