January 30, 2022
I recently found out that Troy Hunt from haveibeenpwned.com migrated his blogging platform to Ghost Pro and loved it. I realized that I hadn't been maintaining my custom blog that was running on Gatsby for over a year and thought I'd give Ghost a try.
The nerd in me timeboxed self-hosting Ghost in Amazon Web Services (AWS) to see how quickly I could get a new blog up and running. My maximum effort to spend was about an hour to complete my "Hello world!" post. The clock starts... now!
Finding the right service
I first checked out Docker to find an official Ghost image - check. Next I dove into Amazon Elastic Container Service (Amazon ECS) since it has "container" in the name and seemed like an obvious win. After some research, I learned it's more low-level than I was looking for, and basically runs a container on EC2. I was looking for bare minimum work on networking, clustering, etc. and decided it was overkill for my proof of concept.
Next I found AWS Fargate, which helps manage the underlying server clustering and scaling that your image runs on for you. This was closer to what I wanted, but still required several steps with networking and configuration to launch a basic environment and the documentation was a bit overwhelming for my simple use case.
I went back to the many compute products and services in the AWS product list and found/remembered Amazon Lightsail. Lightsail is a highly managed service that takes care of a lot of the configuration for you. After a few clicks in the consoleI found a pre-configured Ghost deployment option. Bingo!
A quick Google search led me to an official blog post on setting up Ghost on Amazon Lightsail. I was able to launch an instance, log into the Ghost admin console and write my hello world post within a few minutes. I could have stopped here and called it a successful day, but my excitement told me to take it a little bit further. I decided the next things I wanted was: creating an AWS Cloud Development Kit (AWS CDK) construct to automate this infrastructure, adding a custom domain with HTTPS and possibly Amazon Cloudfront to cache my content for faster performance. I'm well aware that the CDK construct and Cloudfront were premature optimization at its finest, but I was having fun. Diving in and getting in the weeds is how I learn best and I had nothing to lose.
After about 30 minutes creating a custom CDK construct using the Lightsail constructs, I saw that some of the L1 Cloudformation constructs were missing. In particular, the Distribution, Container and Alarm. L1 constructs are auto-generated from Cloudformation, so theoretically they should always be available. Update from the next day: I was about to create an issue on GitHub, and did a quick
npm install aws-cdk-lib@latest to see if it was already fixed and I saw the missing L1 constructs.
I already had a hosted zone in Route 53 with the custom domain that I wanted to use - sean-lawrence.com. Lightsail has its own DNS that it recommends using, so I (incorrectly) followed their instructions and pointed my domain's nameservers to the Lightsail DNS. the next recommendation was to create a static IP so the public IP address would be guaranteed not to change, and then an A record to resolve my custom domain to the public IP address. I couldn't get this to work quickly. There are a number of possible reasons:
- I'm moving super quickly out of excitement. There were several steps that I had already breezed through without reading the details or doing my due diligence. And even if I had been patient in reading everything all the way through, DNS takes time to propagate.
- I later realized that the blog post said to ignore the nameserver update step if you're using Route53. Whoops!
- I jumped the gun and started installing and running Let's Encrypt certbot. After realizing the domain wasn't resolving - likely for other reasons - I tried to delete the cert and start over. It was difficult to figure out how to do that without diving deep into the documentation. I tried deleting the cert files from the instance, but that wasn't enough since it was somehow cached in the system. Running the certbot script again would instantly resolve to the existing cert.
- I did a little foreshadowing on the remaining steps by reading the rest of the blog post and threw in the towel when I saw NGINX. Don't get me wrong, NGINX is a wonderful piece of software. But just like learning ECS, it's usually too advanced for a quick hour-long dive into self-hosting something. For me at least.
At this point, I had a choice to make. 1: Take a coffee break and work backwards for another hour or two to get this up and running. Or 2: jot down what I learned, delete my Lightsail instance and feel extreme gratitude when using the managed Ghost service.
I chose the second option. The amount of money that I would save by self-hosting was trivial, I want to support the Ghost platform and in the past I've fallen into the trap of focusing too much on the technology for my blog. I've written a custom static site generator (SSG) before. Created a full-fledged Gatsby application with a headless CMS backend from GraphCMS. And plenty of proof of concepts in between over the years. I've learned my lesson, and now I'd much rather focus the time and energy I've allocated for my blog into writing content.
I had a wonderful first impression of Amazon Lightsail and I'd use it again in a heartbeat. Especially for hosting things like a Ghost or WordPress since they have managed templates to get started with a few clicks. I'm sure there are limitations that you have over going bare metal with something like AWS Fargate, Amazon ECS or Amazon EC2. And no product is ever going to be a silver bullet that solves every situation. But it seems like an extremely nice platform for deploying highly managed web applications.
In my experience, highly managed > bare metal unless you absolutely have to do something lower-level, already know what you need and have a team ready to manage it full-time. You could argue that you may lose the benefit of saving time with the highly managed solution if you end up having to refactor to a lower level alternative down the road as the application grows in complexity. This is a valid point. I'm currently doing a migration from an autogenerated Amplify GraphQL CLI generated API to a custom API and it's painful. But the time to market we gained by using the auto-generated API was priceless. It also gave us the ability to validate the product and learn exactly what we needed before prematurely optimizing for the future.
Premature optimization is a dance that every engineer and architect has to deal with on almost every decision. I'll stick with Lightsail if I need a long-running application that runs on a server in the future. And if at some point I have to migrate to something like ECS or Fargate, I'll know that I built something advanced and can easily justify diving deep into these more flexible solutions.
Happy hacking! SL