The serverless argument is usually framed as cost, and cost is the least stable part of it. Functions bill per invocation and per gigabyte-second of execution, so at low or spiky volume they are extremely cheap and at sustained high volume they become expensive relative to a container running continuously. The crossover exists, it is calculable from your own numbers, and almost nobody calculates it before choosing.
The shape of your traffic decides more than the volume. Serverless is excellent for workloads that are intermittent, unpredictable or bursty - a webhook receiver, a nightly job, an internal tool used a few times a day, a spike that arrives without warning. Paying nothing while idle is a genuine advantage when idle is most of the time. A service handling steady traffic all day is paying a premium for elasticity it never uses.
The constraints matter more than the pricing in practice. Cold starts add latency to the first request after idleness - manageable for background work, noticeable on a user-facing path, and worst for runtimes with heavy initialisation. Execution time limits make long-running jobs awkward. And the connection model fights databases badly: a thousand concurrent function instances each opening a database connection will exhaust a database that wanted twenty, which is why serverless plus a relational database usually needs a connection proxy in between.
Containers give you none of that elasticity for free and none of those constraints either. A process that stays warm, holds a connection pool, keeps a cache in memory and runs as long as it needs. In exchange you pay for it whether or not anyone is using it, and you own the scaling policy. For most steady-state services that trade is straightforwardly better.
Lock-in is worth being precise about rather than anxious about. A function's business logic is ordinary code and moves easily. What binds you is the surrounding fabric - the event triggers, the identity model, the managed services it calls, the deployment tooling. That is real, and it is also the part that makes serverless productive. Wrapping it all in an abstraction to stay portable removes the productivity and keeps the complexity.
The pattern most teams land on is a mix, chosen per workload rather than as a platform decision. Containers for the steady core services. Functions for the edges: webhooks, scheduled jobs, image processing, glue between managed services. That is not indecision, it is matching the billing model to the traffic shape, which is the only thing that ever really decided this question.