Initially we had a web server (two actually, to achieve zero-downtime deploys) and a database.
A few months later, we introduced Redis for caching. This was much earlier than I expected we'd need to - these days as soon as a web app gets traction, it starts attracting scammers, spammers, and manipulators who put unexpected load on your system.
Just this week, we've finally added a worker server to run long running tasks. Until this week, Java's brilliant Executors were handling this for us in a background thread.
A Java uberjar, deployed to AWS's Elastic Beanstalk, is an approach I'd highly recommend for anyone wanting to get version 1 of their web app deployed.
I suppose I may be overcomplicating things with trying to do it "the right way" right out of the gate. I guess the "perfect is the enemy of done" saying comes into play here.