I run several SaaS apps on a single big OVH server. It handles 6 million non-cached requests per day. The backend stack is pretty basic: Django/Python, MySQL, redis (pub/sub) for websockets. But the secret sauce is OpenResty. I use Lua scripts to do more sophisticated page caching (because the builtin nginx caching is so primitive), DDoS protection, handling websockets, offloading long running requests, and routing between my unix socket upsteams. It's a poor man's Cloudflare in 1500 lines of Lua.
The apps were made long before Docker was a thing, so they just run as regular ol' processes, locked down as much as possible with systemd magic. I originally used uwsgi as my wsgi server, but it turns out gunicorn is vastly more efficient so I use it exclusively now.
I run a warm standby server at Hetzner so I can route traffic there in a pinch. I have a second warm standby running at my house because I'm truly paranoid about automated account bans (despite the very innocuous nature of my business). Backups are at rsync.net.
My single point of failure is DNS. I had a good relationship with DNSMadeEasy so I was not too worried about automated bans. But they were just bought by DigiCert, so that's a problem now.
Payments are handled with Stripe and PayPal. I added PayPal (despite my hatred of the company) just because I'm scared Stripe will ban me without warning, for no reason, and won't communicate with me.
For user uploads, I have an aiohttp Python server that streams files to Wasabi and Backblaze, and caches them in nginx at the same time. So my cloud bandwidth bill is usually 0.
The websocket layer is kind of wonky. Originally, I used the Python websockets asyncio library to do everything. It worked for a while, and then I had to make it multi-process to spread the load. But it was just eating resources like crazy. I decided to use OpenResty's websocket stuff to handle the connections, but I didn't want to write all the complex application logic in Lua. So I used Redis pub/sub to pass messages back and forth from OpenResty-land to a pool of (sync) Python processes. It worked much better. That said, I'm a novice with asyncio, so I could very easily be to blame for the original performance problems.
And sorry, I won't tell you the name of my apps (I don't need any more competitors!)