docker-compose is one of those essential tools that make working with docker so much better. I do use docker directly occasionally, but anything non-trivial, I’d reach out docker-compose immediately. It allows you to “glue” things together and describe the stack in such a neat way.
I currently handle my dev environments with docker-compose, and even some live and staging deployments (like thumbor). I also manage remote database backups with it (using restic, postgresql, stunnel, redis and rdb-tools). In the latter example, it saves me from installing different versions of the database clients and connectors. I am able to instantly upgrade them, and then connect to the remote databases and back them up or restore. It makes the backup system itself immutable and disposable.
Recently however, I started using docker-compose for something that I haven’t considered before: a replacement for shared hosting.
Before we jump to the What and the How, let’s take a small detour to cover the Why… Well, I’ve been using Webfaction for a rather long while now. I think close to 10 years by now. It’s been my go-to choice for simple php sites, WordPress, email forwarding, and even the odd Django or flask apps. This blog used to be hosted there, so were my wife’s online shop, my brother’s websites and a few more.
Webfaction hit this great sweet-spot between developer-friendliness, features and price. It offered SSH access by default, awesome knowledgable support, a choice of data centres in America, Europe and Asia, a simple control panel (not one of those Plesk/CPanel monstrosities) and a pretty great speed and stability overall. All at a very affordable price point.
If I sound like a fan, that’s because I was. (see my previous write-ups from 2012 on how to design an automated failover with Webfaction as well as more advanced caching strategies I could set up on their platform)
Until their buyout.
… and by whom??? Godaddy!!!!!!
What … The … Effing … Fuck.
(I wrote about Godaddy before, and I can’t think of a company I detest much less, and it’s been over 10 years since I wrote about them).
Webfaction announced that all accounts will transition to Godaddy, some time in Aug 2019, apparently (not surprisingly at all, the communication on that was totally unclear).
So what’s the alternative??
The world of shared hosting is a complete and utter mess. There are probably thousands upon thousands of companies offering hosting. You’d think that competition is a good thing, but when it comes to shared-hosting it can’t be further from the truth.
The thing is, there’s virtually no way to tell a good option from a bad one.
What if you pay a lot of money? surely you’ll get better quality. Wrong. Lots of shitty hosting companies are expensive. And they sell a service that’s really hard to measure. How do you measure support quality or performance?
So it’s true that there are some options with a more solid reputation. But since my wife’s online shop is serving Japanese customers, I wanted to find a hosting service in Asia, and a separate one for this blog and other sites, preferably in Europe. I couldn’t easily find a good shared hosting with data centres in Asia. And even in Europe, choices are limited.
It was also a chance to tinker, and see if I can manage a solid solution I could manage on my own.
I actually started-off experimenting with Cap Rover (previously known as Captain Duck Duck). I was really impressed by it, I have to say. It’s pretty much what I dreamed of. Open-source, a very nice web interface, using docker, extremely simple initial setup. I could get a new site up and running, with a Let’s encrypt SSL certificate in a matter of minutes. All on my own VPS, so I still have full root access and control.
Steering the ship
There were a couple of deal-breaker limitations for me though with Cap Rover: Backup/Restore and automation. To be fair, I could have worked around both of these, but it became clear that doing my own thing with docker-compose would be simpler at this point.
Backup / Restore
Cap Rover has limited backup even for its own configs / setup (although it’s something being worked on). Backing up app data (docker volumes) are completely left out to the end user. So if I wanted to back up a couple of MySQL instances and a few
wp-content folders, I would either need to rely on full VPS snapshots or build my own solution. Inspecting the running docker containers on Cap Rover, pulling out secrets (which I would need for the backups) was getting a bit tricky.
This was more of a nice-to-have, but I really felt that I needed a solution that I could get up-and-running just by running a script. Extra bonus to be able to restore the whole thing onto a new VPS from the latest backup. I actually asked about it, and it’s not impossible, but it meant writing CapRover-specific integration with the API to make this happen… At this point it definitely felt that using more common (and definitely more familiar) automation tools would be a better fit for me personally.
So here’s what I embarked on building, what it allows me to do, and the building blocks I ended up using.
- Fully-automated bootstrap and deploy process, including restoring from backups. Give me a new VPS and I can get a running system within a few minutes (excluding DNS updates…)
- nginx-proxy docker container to dynamically serve all apps
- SSL certificates using Let’s Encrypt (optional) with the letsencrypt-nginx-proxy-companion
- Adding an app, database or any other component by updating my docker-compose file and running
- Backups and restores are using restic, with a daily cron, and a couple of simple scripts to dump or restore the database (backups are stored on S3)
- Automation using python fabric (v2)
- Secrets are managed with Bitwarden (using my own envwarden, you can read more about it here)
- Added security with
fail2ban(and even custom rules to prevent wordpress brute-force attacks)
No free lunch
All those things are pretty great, but there are obvious trade-offs here. It’s not a one-size-fits-all solution, there’s no support to fall back on (other than the VPS support), it’s definitely a very “bespoke” solution. It’s not nearly as refined as Cap Rover for example.
I did think about “productizing” it (coming up with a generic solution that can be open-sourced, for example). To be perfectly honest, I can’t think of a way that won’t make huge assumptions about the apps themselves (e.g. how to back them up and restore them), or essentially be a collection of scripts, conventions and tools, which are quite far from being a single product.
So for now, I’ll stick to the latter. I’m considering publishing the specific scripts and processes on a follow-up blog post or on Github, but not really sure if there’s an actual interest in this, and it will likely go stale over time. If you are curious about more concrete details, please don’t be shy and let me know :)