This week's dispatches from the Ministry of Intrigue
Hello, faithful reader.
We published the following fresh dispatches this week:
Notes on My Latest Server Migrations with Coolify
July 28, 2025
Two weeks ago on Mastodon, Jeff Triplett (completely unintentionally) nerd-sniped me with his posts on migrating away from Digital Ocean and Fly.io. Reading through his journey, I discovered that I’ve been radically overpaying for my VPS services. I’ve been a happy Linode1 and Vultr customer for years, but Hetzner’s pricing is in many cases half the cost of VPSs on those platforms, and you get more resources to boot!
Now, I had heard of Hetzner a few years ago, but I didn’t know much about it, and at the time it was only offering servers in Europe. So I shrugged and moved on. Jeff’s posts made me look again and I saw they were now offering US-based servers (where most of my traffic happens to be), and while the US prices were higher than their German/Finnish counterparts, they were still significantly cheaper than my current boxes. I dug around the internet seeking people’s experiences with Hetzner’s quality and stability and in general came away with a good impression of them. With that in mind, I knew it would be irresponsible for me not to consider moving resources to their platform.
But that in and of itself was not the true nerd snipe. No, the true trap was Jeff writing about his experiments with Coolify, a self-hosting tool that you can connect to your bare servers and use to manage a variety of installation workflows. Under the hood it ends up turning everything into a container, but can interpret your applications using either Nixpacks, Dockerfiles, or Docker Compose. It handles setting up the reverse proxy for each combination of project, server, and environment for your application. As a bonus, the whole project is Apache 2.0 licensed, and there are no differences in feature set between their commercial hosted service and the downloadable self-hosted version of its tooling. It aims to serve as an alternative to PaaS offerings such as Vercel and Heroku.
I decided to give it a try, and this, dear reader, was what turned a simple migration into a week-long process.2 Because, in order to use Coolify, I had to add support for Docker-based workflows, so this had become a matryoshka of projects.3 You see, most projects on my hosts were happily running alongside each other as fully isolated user processes managed by Supervisor using a single nginx server per box as their shared reverse proxy. This was a very familiar way for this old man to manage them. I did have experience with PaaS deployments, specifically Heroku and Fly.io, but both had been way too expensive once you added up all the processes, and they each had their own technical headaches to boot.4
Dockerizing my projects #
Adding Docker support for my projects both wasn’t as hard as I expected it to be, while also being a frustrating experience from a networking perspective. Coolify, by default, uses Traefik for its reverse proxy, and I found the latter a nightmare to debug. Especially when I couldn’t figure out if the issue I was encountering was something in Docker (even if the image ran happily on my machine), Traefik, or something else Coolify was doing. Friends, I got so frustrated at one point, I went and canceled my Coolify subscription, and tried to switch gears to doing something with Dokku. The latter got the project I was banging my head on up in running in about an hour, but then almost immediately started throwing random downtime. I was honestly about to give up and just go back my tried and true old-school method when I had an epiphany. I reinstalled Coolify, but changed the proxy to Caddy which was a hell of a lot easier to debug. The problem was, in fact, part of my project configuration, but I couldn’t see that when I was using Traefik, even when I put its logging into debug mode.5
Another issue I encountered was Coolify doesn’t allow you to specify a different command to a given Docker container, which is annoying when you use the same image for both web and worker processes. You can get around this by using Docker compose to specify the different commands, but that requires you to handle more and more of the configuration within the compose file as opposed to the Coolify interface, which is annoying. It’s not terribly difficult to do, but it does mean you have to spend some time thinking about which parts Coolify will still do for you versus what it needs to have in the compose.yml
file to run correctly.6 Once I had that problem solved, I turned to getting everything else up and running.
This went much quicker because I had a better sense of what to expect, and then I could move on to my other self hosted services.
Migrating my services #
I have two other services besides my projects that I self-host: Nextcloud and my Matrix homeserver. The latter was relatively trivial to do, but Nextcloud encountered some roadblocks. The official Nextcloud image contains its own Nginx server as reverse proxy, and getting Nextcloud to accept the forwarded protocol from Coolify was something I ended up banging my head on for a while. I spent more time than I should have trying to troubleshoot this problem, and in the process spent a lot of time digging through Coolify’s source code to figure out where certain values were getting populated from. Then I migrated my data, set up my new end-to-end encryption keys and had all of my services moved!
Finishing touches #
Now that everything was running in its new home and the DNS was resolving correctly, it was time to make sure backups were working correctly. Coolify seems to be doing a good job backing up the data stores that it manages, sending those files to a B2 Storage bucket I set aside for them. For any non-managed data stores I set up my usual backup scripts and rotation tools, and then used Duplicati to encrypt those backups and sync them to a different bucket. After a couple days of monitoring this, I was able to safely decommission my Linode and Vultr servers.
Final thoughts on Coolify #
While the initial process was frustrating, I’m quite pleased with the results. Everything seems to be running quite happily inside of Hetzner with Coolify, my monthly hosting bills are over 50% cheaper, and I still have lots of system resources to spare!
I will cautiously endorse Coolify. It has a wide variety of tooling that would be a pain in the ass to manage or write myself, and is a pretty powerful system. Unfortunately, the documentation is pretty limited beyond how to install and set up Coolify itself, so a lot of details of how to use Coolify as an application are pretty sparse. Ultimately, I did end up resuming my subscription, because I do see the value in it and want to support its continued development. I may ultimately still move to the self-hosted version, but for now I’m quite happy.
-
My primary Linode server had been around for a shocking 15 years! ↩︎
-
In fairness, this was mostly because work has been extremely busy of late, so I was only able to poke at this for brief periods at a time. ↩︎
-
In general, I find Docker containers to be a frustrating experience, especially when it comes to networking and debugging. ↩︎
-
For example, I have a Django project that I use to store quotes from Explorers Wanted episodes and provide an API for retrieving either random quotes or using Markov chains to generate horse_ebooks-style sentences based on given characters. This can run happily with not many resources, but due to the use of Spacy for natural language processing, there’s no way to keep the image size smaller than 500MB, which often choked Fly’s builders, or caused OOM errors in the runtime. I could have helped it by increasing the size of the Fly machines, but by that point, we’re getting price increases that aren’t justified for a small hobby project. ↩︎
-
It’s very easy in Coolify to switch between the proxies, so you can swap back and forth to debug if necessary. That being said, the Coolify proxy documentation warns that Caddy isn’t as supported by them as Traefik, so after debugging I swapped back to Traefik. You know, just in case. ↩︎
-
More user-facing documentation would help here. ↩︎
And that's it!
Grave dust and falling leaves.