Stuart Thomson

Stuart Thomson

The way I host the things I’ve run has changed a fair bit over the years. I’ve previously used cheap virtual servers, attempted to understand major cloud providers, and even dabbled in Kubernetes. None of those really fully sat right with me. Managing a VPS was ok, but required more maintenance than what I was looking for. Major cloud providers made simple things more complicated and with the added fear of slightly messing up and draining my card. If I thought cloud providers added complexity, then I was not prepared for my dabbling with Kubernetes. In doing each of these, however, I learned more about what I wanted out of whatever hosting I used. I also learned how much effort I was willing to put in up-front and over time.

One thing to keep in mind is the kinds of things I make in my own time. They’re usually simple single-purpose web applications. I’m not building full-on SaaS sites, and I don’t make any money from them (nor do I aim to at this point). As a result I tend towards platforms that have free offerings, as even small amounts add up over multiple tiny projects and months.

Static sites

Any static websites, like screenshot.help, are hosted on Netlify. I’ve been hosting things on Netlify for several years, and it has been great for hosting static sites with its git integration. I also use its manual deploys to hold archived versions of sites that I am no longer maintaining, so that that information is not lost.

There is one exception to what I just discussed: this blog. It’s a Next.js app, but with the client-side Javascript turned off. Since it’s built with Next.js I deployed it to Vercel, as Netlify hadn’t finished their Next.js support at the time.

Web apps with backends

A couple of the things I run also need to have a backend running. For those I use Fly.io. After understanding how their configuration file works, I was able to get containerised backends running very quickly. linkdrop, which uses Remix, had a fly.toml in the boilerplate, which meant it was basically ready to go from the start.

I do want to give Railway a go at some point, so I can compare the two. I like the direction that the newer hosting platforms are going, where less configuration is going further, and the more boilerplatey stuff (such as ports and certificates) is being taken care of automatically.

Domains and DNS

All of my DNS configuration is in Cloudflare, and is specified as Terraform configuration in a private GitHub repository that I intend to use for anything infrastructure-related. The Terraform configuration is run in Terraform Cloud, which also holds the state.

The infrastructure repository was previously home to a lot more configuration, as it’s where I had the Terraform configuration for a Kubernetes cluster I was experimenting with, as well as the two things I had running on it. Predictably, Kubernetes is not a good fit for something that should probably just be a VM, but I still value the learnings from it.

Things I didn’t write

I also run a couple of extra things that I didn’t write. For now this is

  1. Syncthing, as an alternative to Google Drive,

  2. Uptime Kuma, for monitoring anything I run, and

  3. Umami analytics, which I’ve been trying out instead of GA.

Since I didn’t write these services, I can’t just include a fly.toml in their repositories. Instead, I’ve added some extra Fly configuration to the aforementioned private infrastructure GitHub repository.

I need to keep these services up to date (as it would be very easy to forget about them), and I also want to keep the effort to do that to a minimum. I could update the build.image field of the fly.toml, but I would need to manually update this. Instead, I’ve added a separate Dockerfile that only has a FROM step. I then use GitHub’s dependabot config to automatically create pull requests for these Dockerfiles. On push, a GitHub action is run to do a deploy of each application to Fly, which is skipped in case that nothing changed.

docker
# Example Dockerfile for syncthing
# This sits alongside the relevant fly.toml
FROM syncthing/syncthing:1@sha256:<hash>
# Dependabot creates PRs to update the hash

Having a Dockerfile with only a FROM step looks a bit strange at first. Since its the only instruction in the file, no new layers are created in the final image, and the only difference would be metadata. Despite the strangeness, this has massively streamlined the process of keeping these services up-to-date.