The Infrastructure Hosting This Site
I’ve recently taken some time to re-work my website from a hand-coded resume site1 to modernize it and include a blog. Hugo does all the heavy-lifting for the site content which is great since I’m not a web developer. Now that it’s all set up, I just have to write markdown to produce content.
I’ve been hosting the old version of the site with AWS S3 + CloudFront for a few years, but it wasn’t documented well and some parts of this broke when migrating the content to Hugo. This post a snapshot of how I’ve configured my domain and site hosting infrastructure, why it’s like this, and how much it costs.
Infrastructure Requirements
Behavior
I wanted the infa for this site to be configured such that:
- Static website content is hosted at https://www.chriswlucas.com
- All of the following alias domains redirect2 to
www.chriswlucas.com
:chriswlucas.com
chriswlucas.org
www.chriswlucas.org
chriswlucas.net
www.chriswlucas.net
- Any request using
http
protocol is automatically redirected to usehttps
- Redirection for any reason must preserve the page of the request
- e.g. http://chriswlucas.net/about/ redirects to
https://www.chriswlucas.com/about/
- e.g. http://chriswlucas.net/about/ redirects to
Tools
I have chosen to set up the infrastructure with AWS mostly because I have experience with AWS. I’m happy with the amount of control I have over the infrastructure while also having powerful tools to write infrastructure-as-code. I’m sure other providers have similar offerings but this is what was easiest for me.
I’m using AWS Route53, S3, Certificate Manager, and CloudFront for the actual infrastructure.
The infrastructure-as-code is using AWS CDK (v2) which enables me to write TypeScript code to generate the infrastructure and deploy it as a CloudFormation template.
How it works
Components
Domains
- Domains registered with AWS Route53:
chriswlucas.com
,chriswlucas.org
,chriswlucas.net
- Route53 hosted zone created for each domain for DNS
Hosting www.chriswlucas.com
The overall behavior is that a request for a page on www.chriswlucas.com
hits a CloudFront distribution - which serves the page from its cache if present or retrieves the page from S3 and returns it while caching it for later requests if not present.
- Static website content synchronized to S3 bucket named
www.chriswlucas.com
- Certificate for
www.chriswlucas.com
registered with AWS Certificate Manager (ACM) - CloudFront distribution to serve web traffic for https://www.chriswlucas.com
- uses the certificate to enable
https
- acts as a read-through cache when fetching requested objects from the S3 bucket
- included a CloudFront function to re-write requests which is described later
- uses the certificate to enable
- DNS
A
record in thechriswlucas.com
hosted zone pointingwww.chriswlucas.com
to the CloudFront distribution
Redirecting chriswlucas.com
to www.chriswlucas.com
The overall behavior is that a request for a page on chriswlucas.com
results in a 301 redirect for the page on www.chriswlucas.com
- S3 bucket named
chriswlucas.com
configured to redirect any requests to the DNS namewww.chriswlucas.com
- Certificate for
chriswlucas.com
registered with ACM - CloudFront distribution using certificate to host https://chriswlucas.com
- DNS
A
record in thechriswlucas.com
hosted zone pointingchriswlucas.com
to the CloudFront distribution
Redirecting each <alias domain>
and www.<alias domain
to www.chriswlucas.com
Largely the same as the section above to redirect chriswlucas.com
to www.chriswlucas.com
. That S3 + ACM + CloudFront infrastructure block is repeated for each alias domain with the modification of the certificate and distribution also being configured to accept the www
subdomain as an alternate name.
Architecture diagram
Here’s a picture of how it is all connected
Are all those CloudFront distributions really necessary‽
S3 buckets can support website hosting themselves, but do not support https
endpoints. Since I want to have https
, the best solution within AWS is to use ACM for the certificate and CloudFront for the endpoint. So to even have the site use https
I need to have a distribution for www.chriswlucas.com
.
However, I want https
redirection to work from any of the alias domains. So, that requires hosting those domains with https
too. Therefore, each of the S3 buckets configured for redirection needs it’s own CloudFront distribution in front of it to support even accepting traffic on https
before redirecting it.
What’s that CloudFront function?
Hugo uses “pretty” URLs by default and I like them.
# pretty URL (default)
/posts/the-infrastructure-hosting-this-site/
# the alternate ugly URL (uglyurls: true)
/posts/the-infrastructure-hosting-this-site.html
However, CloudFront doesn’t have a native feature to append index.html
to the end of arbitrary requests. As an example, the URL bar of your browser likely ends with /posts/the-infrastructure-hosting-this-site/
right now. But the page you’re reading is actually served from /posts/the-infrastructure-hosting-this-site/index.html
3.
To enable using the pretty urls, I’m using a CloudFront function (a feature launched in 2021) - another option is to use Lambda@Edge but that is more complex than necessary for this feature and also costs more.
The aws-samples repo includes a function to re-write URLs that works perfectly for pretty URLs so this is what I use to make it work.
Cost
At the current traffic levels the site amortized cost < $5/month. This cost will increase over time if traffic increases and I will be watching it, but it’s good to know that the “scale to zero” cost for all of this is about $5/month and more than half of that is in the domain registration fees.
Route 53
- three domains cost a total of $35/year or $2.92/month
- $0.50/month for each of my 3 hosted zones -> $1.50/month
- DNS queries -> $0.01/month
S3
- storage -> $0.35/month
- requests -> $0.01/month
CloudFront
- requests (free tier) -> $0/month
- bandwidth (free tier) -> $0/month
- function executions (free tier) -> $0/month
Is it worth doing all this?
Possibly not. Before using custom infrastructure for hosting, I used GoDaddy DNS and NearlyFreeSpeech.net for hosting and that was slightly cheaper since I didn’t have to pay $0.50/month for the privilege of having DNS for the domain. I was particularly impressed with NearlyFreeSpeech.net
as I was able to host my resume site for pennies per month over the span of several years.
However, I think the idea of self-hosting, working directly with infrastructure, and utilizing infrastructure-as-code is cool enough to going through all this effort. And since all the infrastructure is serverless and scales down to $5/month with no traffic, I don’t have to worry about maintaining it on a daily basis or paying too much if no one is looking at it.
Future work
The infrastructure does not yet support building and deploying the website automatically. In the future I’d like to set up a mechanism that listens for commits on the website content repo which builds the website and deploys it to the S3 bucket automatically. Having continuous deployment would be great, it just didn’t make it into v1 since Hugo has built-in support to deploy to S3 + CloudFront.
The code
All of infrastructure described here is infrastructure-as-code using AWS CDK and available on GitHub. Anyone can fork the code, make a few configuration tweaks, and have identical infrastructure up and running.
The only bits that are not automated are registering the domains as registering a domain is not supported by CloudFormation/CDK.
-
The previous version is still available on GitHub. ↩︎
-
My old website was available at multiple domains without redirection. For example, If you navigated
chriswlucas.net
then that name would stay in the URL bar after loading the site. This was slick, but doesn’t nicely with absolute URLs in general and specifically broke the Hugo site when setting a domain as the baseUrl. There are other ways around this, but it sounded easier, more robust, and more like the behavior of other sites to just serve the site at one domain name and have any other alias names redirect to primary one. ↩︎ -
you can go there directly ↩︎