Back

How I keep hosting cheap

If you've been around my portfolio a lot of things that I've hosted are either on vercel or on my VPS and I've had a post before that talks about how selfhosting might not be for you.

Compute isn't free but it's definitely can be made cheaper if you do something similar. My apps or rather the delivery artifacts for most of these can be split into the following categories.

Static Content

These are normally hosted on Cloudflare or Vercel and are pretty much free for the most part. I don't use Next.js or other frameworks, it's mostly static HTML/CSS/JS generated by some utility. A good example is the site you're reading right now. Which is a bunch of markdown files that are converted to HTML by alvu.

Also, vercel because there's no surprises when it's just static content. If you are using their functions / dynamic rendering, you might need to research the pricing and the edge cases before you go in that direction. Most of the pricing on vercel can be fixed with good caching but you still need to learn that so I keep myself away from that for now.

Binaries

These are things like hits, which is a simple crystal lang app that keeps track of page views with the URL as the identifier. Also because of how efficient the language is the ram and CPU usage are very low so even for slightly more complex apps I end up using a VPS from DigitalOcean and it almost always starts with the same setup which I'll discuss about later in the post.

Containers

This is where things get a little more CPU and RAM heavy since the containerisation engine ends up taking quite a bit of resources and when dealing with apps where you want something like this you end up having to take a slightly larger compute instance to make sure the app doesn't choke immediately. Pretty obvious stuff but we'll get to that as well.

VPS

For the longest time, I've been with DigitalOcean for my personal projects. I've worked with AWS and Azure in detail for my day jobs but it's always been DO for personal projects. I guess the simplicity of just being able to spin up droplets was what got my attention in the beginning and then I just got used to the platform and terraform took care of making me even more lazy when working with it.

I wish this was sponsored but nevermind.

Moving on, the setup for most of the apps I host is the same. It starts with the $4 droplet with 10GB of SSD storage and 1 vCPU core. That's pretty small if you ask me but that's what's been running the Hits app for more than a year now. The problem is that the 10GB SSD when you involve a database like SQlite and then the logs and database take up most of the space in around a month or so. Probably quicker if your app goes viral (never had to worry about that).

Like most providers, DigitalOcean comes with a Block Storage that you can attach to the droplet and I make sure to mount this up on /mnt/data and then the sqlite file is stored there. This doesn't solve the problem of linux's own systemd logs(systemctl and journalctl) taking up space, these creep up slowly so you find about them a lot later than you'd like.

For this the solution is to make sure you update /etc/systemd/journald.conf to set the SystemMaxUse property like so

SystemMaxUse=1G

After making this change, you need to restart the journald service for the changes to take effect:

sudo systemctl restart systemd-journald

That should save you from having to run journalctl --vacuum-size=1G manually every few days or running that as a cron, while the cron option is viable it's easier to just let systemd handle it.

The block storage isn't cheap to start with, as ~20GB would cost you ~$2 per month. So you're paying close to $6 for the compute + storage, and you could just get a $6 droplet that would come with 25GB of SSD.

I do this because,

  1. I get 5GB extra SSD space compared to the $6 droplet. (I can use that for swap instead of giving up the precious 1GB from the root 10GB SSD)
  2. I can easily upgrade to a larger droplet without paying for larger SSDs and increase the storage as an when needed. This control gives you the option of only increasing what's causing the bottleneck. If it's storage, you increase the block storage, if it's memory/CPU then you just increase that instead of having to completely upgrade to a higher droplet that may or maynot suffice your use case.

An example for this is, let's say you use a docker container for an app, a 1GB droplet with 2GB Swap should be enough for you to run the app with docker but docker images and overlays take more space than memory, so you end up moving from a $6 droplet to a $12 droplet to get a 50GB SSD which will get filled up with build cache and docker images over time as you deploy more versions of your app.

Instead if you go the block storage route you can have docker treat /mnt/data as the root data directory instead of /var/lib/docker and you don't need to move to a bigger droplet unless needed. It's a convenience tradeoff if you ask me which differs from person to person.

One off services

For services like CRI, they get hosted on Fly.io for a simple reason, I don't need the site running all day long and the cold start is okay for me.

Good thing about fly.io is that if the billing doesn't cross $5, I don't get billed and I honestly love that approach, I do have services that I wish to host there so that I do end up getting billed but I suck at getting users so that's that.

Add ons

If you do end up using managed services from DigitalOcean then this workflow might not work for you. They are all priced similar to their competition so it can get pricey over time and you should do that only when you see your app getting enough traffic and attention.

Terraform / OpenTofu

So, I do have an TF file that does the above for me. It automates the process of spawning the $4 droplet and a 20GB block storage volume attacked to the droplet. The file also has a user init script that takes care of installing caddy and making sure the droplet is ready for me to use. The variables allow me to move up and down between the droplets. For example, I need the 1GB RAM ($6 droplet) for apps where containers are used. The data init script also takes care of creating a swap file on the block storage volume , which is slower than a swap partition on the root SSD but it's better than not having one.

That's all the information for now.

Adios!