Write Once, Read Many - Dynamically Static Websites

The web is a dynamic place, no doubt - but like many applications, a typical website (like this one!) is read many orders of magnitude more frequently than it is updated. Even something seemingly rife with churn like a comments section is still dominated by reads.

History has taught me this lesson several times over. First, I tried building web content management system that used the .Net CLR embedded in SQL Server to render HTML as pages were loaded. Still having nightmares over that one - but hey, I learned from it! Next I was introduced to the idea of rendering out a product catalog periodically, which greatly reduced & smoothed the load on our database servers and enabled our website to be almost entirely static.

Taking this concept a little bit further, we came up with applying the same idea to our content management. By “compiling” our content to static HTML, we were able to host the site using “dumb” servers, and holy shit did it scream! Never again did we deal with load issues, scaling was almost perfectly horizontal, and reasoning about what the site would serve for any given request was so much more straighforward.

For those of you with a security oriented mindset - how does having zero dynamic code being executed to serve your website sound? Pretty sweet, right!

Using this idea

Naturally, I wanted to take advantage of the same thinking when I started putting together this site, and as luck would have it we were far from the only people thinking the “dynamically static” approach made sense. A bit of cursory research led to the Hugo framework for managing a website and all the requirements I had were quickly checked.

I’ll spare you most of the details about how to use Hugo, as their website does a much better job of explaining it anyway. Instead, let’s focus on what is important for the rest of this article.

Reference Materials:

The basics

Hugo is highly convention-driven – it expects your layouts in one place, your content in another, and config in another. Posts are written using markdown (another of my longtime favorites!) and this simple structure allows your site to be trivially managed using git, instantly enabling a multitude of branching, versioning, and other ideas.

There are a ton of themes readily available, meaning you can get a beautiful website together with minimal design effort. If you’re more hands on, the approach to building the layouts is dead simple.

Since the “compiled” output of your site is a collection of html, css, and image assets hosting is incredibly simple. I’m hosting this site on AWS S3, fronting it with AWS CloudFront as a CDN, and using a free custom TLS certificate from Amazon Certificate Manager. All of these resources are managed using terraform, and cost me pennies a day. Not bad!

My neuroses: container based SDLC and branch-driven environments

I’m not a huge fan of having a ton of random tools running on my machine and as a result I generally create self-contained “dev” environments using either a virtual machine or a docker image. In this case, a docker-based approach made the most sense. Further, as I love to experiment, I generally try and automate environment creation based on a branching model - each branch in my repository gets a dedicated deployment, and that deployment is automatically torn down when the branch gets merged & deleted.

Putting it all together

The sample repository demonstrates how it all comes together - the infrastructure for hosting the site is all in the infrastructure folder, website content is rather unsurprisingly hosted in the website folder. There is a Dockerfile that codifies the development & build environment and enables local hosting, Finally a few helper scripts to handle build, spawning the dev server, and deployment – these are the same scripts that are executed by the CI/CD pipeline which is codified in the azure-pipelines.yaml file.

Dynamic creation of environments is handled primarily by the deploy.sh script. It takes the current branch name and uses it to create a “safe” prefix. This prefix is used in the URL and to create a terraform workspace.

Thoughts?

I’d love to hear ideas for improvment or updates that could be made! Please feel free to reach out either in the comments or via pull request to the repository.