For a while, we (Bloom Ventures) used Heroku (nowadays, would probably use fly.io), with hosted Postgres, and it worked fine with Clojure.
To cut costs, we got our own VPS (say, with Hetzner), and have been managing things ourselves. For a long time we would:
uberjar locally (in a clean repo), scp a jar to the server, and then
java -jar myapp.jar. Multiple apps on the same server. Postgres on the same server. No docker. For web apps, you’ll want a proxy like nginx in front of your app to allow for multiple domains on the server and to take care of SSL.
At some point, we threw supervisor · PyPI into the mix: it monitors the processes, restarts when they (rarely) crash, rotates log files, etc.
We use Ansible for our deploy script (I highly recommend some form of reproducible deploys, but, I don’t recommend Ansible; every complex app eventually begets a complex build, and with Ansible, you end up “programming via yaml” which is hell). Our script has evolved to build on a temporary vanilla system, run tests, and do 0-downtime deploys.
Kubernetes does a lot of nice things, which I often wish we had, and I respect the “12 factor” philosophy, but our approach has been “good enough” (and we don’t have the bandwidth to learn how to deal with kubernetes when things go wrong). Nothing stops you from deploying Clojure uberjars to kubernetes with one of the various hosted-kubernetes providers, or Heroku-clones.
We have a philosophy of “avoid building a system for as long as possible” and try to keep things within Java-land (ex. using Hazelcast instead of Redis), within the same process (ex. bundling Lucene, rather than using a separate Elasticsearch server), and even within the same mono-repo (even if we want to run multiple seperate services, we bundle them together and launch them with different command line arguments). The main exception has been the DB (usually Postgres, but you can go quite far with H2).
As a startup, you can scale pretty far vertically on the cheap. Then as demand (and presumably revenues) increase, scale further vertically (ie. pay for a more expensive server with more CPU and RAM). Then, when that hits its limit, you can do more serious “systems design” and engineering to scale horizontally. When we went through a similar transition, the Clojure app didn’t require that many changes, most of the scaling tends to happen outside the code (ex. sharding postgres, setting up a seperate load balancer, improving the build system to now manage multiple servers, etc.)
If you’re Google/Facebook/etc., every product you release starts with Google-scale load on Day 1, so you have to do a lot of systems-design before launch. As a tiny startup, your load will start at 0, and will hopefully scale with your revenues. Too much demand is a “good problem” to have.