I want to upvote this 100x
Do not underestimate the power of a single server to host you app. Sure it won't work in _all_ situations but omg you can get so much out of a single $30/month VPS .. we've been indoctrinated that everything needs to be on hyperclouds and mega scale. But that brings so much cost and complexity that most applciations don't need.
> you can get so much out of a single $30/month VPS
I agree with this 100%, but only wanted to note, that not all VPSes are equal. Having worked in shared hosting business in the past, I can tell from experience that performance can vary greatly depending on hosting provider and how much they have over provisioned their virtualization platform, since VPS is nothing more than a VM running on someones else's hardware and that someone can put 4 VMs on single CPU core - all fighting for same CPU time, so it's depends on certain luck what will your VM get and what neighbors are doing - idling of being hacked and mining crypto. So if requirements are serious, look out for dedicated core VPS hosting and stay away from too good to be true cheap VMs deals. Also relevant monitoring metric is CPU steal time - its percentage of time a virtual CPU waits for a physical CPU from the hypervisor while it is busy serving another VM - that is strong indication you are being ripped off CPU.
100%. And super easy to scale up to a certain point. Alternatives have it's place though (PaaS is excellent for 100% product focus in limited timeframe, cloud/orchestration when you have scale, Kamal in Rails world is a neat middleground for some extra robustness).
Sure, but you need the knowledge to set it up. And that is what I do :)
I moved all my stuff from AWS to a Hetzner VPS recently. I don't have much, and AWS was actually cheaper, but I'm so much happier having everything in one, simple spot.
There's a gap in my knowledge so far, which I think is mirrored in this post: I have been piecing together my server by hand, and I _know_ I will regret this at some point, but I don't know how I want to solve this yet. I don't want to involve Docker in this setup. Perhaps I should go back to Saltstack or Ansible, or maybe there's something in Nix for me, or snap/flatpack maybe, I don't know. There's a good chance I'll just never solve it, but it seems like there's a gap there that's waiting for a great, simple, small solution (or it exists and I just don't know about it).
So after all these years (decades now) of learning and working in linux every, single, day, I still have a lot to learn! :D
You are not wrong. Docker (paired with Kamal in Rails world) would simplify a few bits of the setup. But not all. The reason I haven't switched is:
1. my lack of experience with running docker in production
2. don't fix something if it ain't broken
But I'm planning to revisit it when my current LTS version runs out of support. Also, Kamal defaults change a few core pieces (caddy vs nginx, puma vs passenger) so there's a bit of extra learning curve). Oh and you'd still need to harden the server and keep it up to date.
And why not Docker? I feel reluctant to suggest it even though you explicitly declined it, but it really does seem to be the best solution.
I initially reached for Docker actually, but when I started researching how to run it securely, I just thought "I don't need this. Systemd is already there and does all of this in an easier and more direct way".
One Rails app self deployed in VPS can really go a long way.
I'd add:
- Learn tailscale. It's one of those technologies that takes half a day to get used to (claude code will answer all your questions), and you'll never want to live without it in the future. SSH into your VPS, install tailscale and use `tailscale set --ssh`, and shut down `systemctl stop ssh.service`. You don't even have SSH daemon runing anymore, extra safety.
- Use a simple docker compose to run your dependencies like docker. Bind to the tailscale IP, so ports: {{ tailscale_ip_of_this_server}}:5432:5432. This way your database is not exposed to the web, but it's exposed to the tailscale. Use tailscale_ip_of_this_server in your ENV vars to rails to connect - since it's running in the same server, tailscale will route it to localhost (and with localhost latency).
- With claude, set up a second VPS, add it to the same tailscale, turn of SSHD. They can now connect directly via tailscale (try ping or sshing between them).
- Then, install claude code on this second VPS. Since postgres is bound to the tailscale IP on the main, the 'slave' can connect to it. Install postgres via docker, but have claude set up a 'streaming replication' between server MASTER and server SLAVE, using tailscale ip.
100% secure, encrypted, and now you have a postgres replication with zero data loss if something goes wrong.
In the same SLAVE, you can also install barman - claude code will help you set it up.
You then have a postgres BACKUP with point in time recovery.
There you go!
Something worth adding to the list: Enable rate limiting.
I'm also running my business on a single server, works perfectly, except for one time when someone tried to find some content with hash IDs through bruteforce. No problem, a tiny VPS can handle one malicious user. Except the amount of errors logged by nginx filled up the disk.
Good point. I have experience with Rack attack on application level. Would you recommend webserver instead (nginx)? Or even Cloudflare? (I bet they have a solution).
This looks like a "send-only" server.
> sudo ufw default deny incoming
Seriously, what does one do when accepting connections, given the onslaught of data-hungry bots out there?
I wouldn't want to deal with that in any upcoming planned servers and services.
You put your reverse proxy on a publicly available machine then through strict firewalls only accept communication to your back end from the reverse proxy; effective leverage VPCs to make your backend not be on the public Internet. That should allow you to filter out malicious users without affecting your actual application and it's trivial to scale your reverse proxy horizontally or reach for a WAF if you have the need/desire.
I'm using external "send-only" SMTP server (Sendgrid) and Google Workspace as receiving/sending. Email itself is something that I'm not keen on DIYing (though I looked into it and other SMTP alternatives).
its a typical web server setup. Only incoming allowed is http, https and ssh.
Note 2 says it uses Sendgrid for email. The server is for the web app.
Nice setup! I think containerization is worth considering if you have multiple applications.
I run 5-7 on one server, with DB, using CapRover on a $5/month Hetzner server. Serve probably around 5k users.
Thank you! I've decided to keep one server per "serious" application. And I have one with a few toy projects that are not critical. Containers would make a lot of sense there. Still containers would make the setup story slightly easier even for this one server (and things like upgrading ruby versions, ...).