There is one question that decides this, and it is not about scale or cost: does your code wait for something, or does it answer something?
Code that answers a request and exits is a function. Code that waits, whether on a socket, on a queue, or on a timer it owns, is a process. Everything else follows from that.
What serverless is genuinely good at
Bursty, request-shaped work. An API endpoint that gets called a thousand times an hour and nothing overnight. A webhook receiver. An image resize on upload. You pay for the invocations and nothing while idle, and it scales out without you thinking about it.
If that describes your workload, use it. This article is not an argument against functions.
Where it breaks down
A function has a lifecycle: start, run, stop. That lifecycle is incompatible with holding a connection open. A Discord gateway app, a Slack Socket Mode app, a queue consumer, a Telegram long-poller. All of them need to be running when nothing is happening, because being there when nothing is happening is the job.
You can bolt persistence onto a function with a keep-warm ping, and it works in the demo. It fails on the deploy that changes the schedule, the cold start that drops the first event, and the bill that arrives for invocations that did nothing.
The decision in one table
| YOUR CODE | WHAT IT WANTS |
|---|---|
| Answers HTTP requests, bursty | Serverless |
| Holds a websocket or gateway | Always-on container |
| Consumes a queue continuously | Always-on container |
| Runs on a schedule it owns | Always-on container |
| Runs on a schedule the platform owns | Either, scheduled function is fine |
| Keeps state in memory between events | Always-on container |
| Cold start would be user-visible | Always-on container |
Cost, honestly
For always-on work a small container is usually cheaper than the equivalent kept-warm function, because you are paying for a fixed slice rather than per wake-up. For genuinely spiky work the opposite is true, sometimes dramatically.
The trap in both directions is the same: estimating from the happy path. A function priced on expected traffic gets expensive under an unexpected spike; a container priced on peak sits mostly idle. Neither is a scandal, but it is worth knowing which mistake you are making.
You can use both
Most real projects do. The app holds the gateway connection in a container; the dashboard behind it is a set of functions; the nightly report is a scheduled job. Splitting on the wait-versus-answer line gives you an architecture that costs what it should.
Spocket only does the always-on half, deliberately. If your code answers requests and exits, a function platform will serve you better and cost less.