Home/ Blog/ Article

Serverless or containers: choosing cloud infrastructure for a small engineering team

ยท

Most write-ups on serverless versus containers treat it as a technology preference, something you pick the way you pick a linter. For a small engineering team it isn’t that. It’s a decision about who does the operational work you can’t see on a pricing page, and it has a direct effect on how many people you need to hire and how fast you can ship the next feature. Here’s how to think about it properly.

What you’re actually choosing between

“Serverless” here means managed function or container platforms: AWS Lambda, Google Cloud Run, Azure Functions, Fargate. You hand over a unit of code or a container image, and the platform handles scaling, patching the underlying host, and billing you per invocation or per second of execution. You don’t manage a cluster.

“Containers” here means you or your team own the orchestration layer: Kubernetes (self-managed or a managed control plane like EKS or GKE), or a simpler container host you run continuously. You decide how many nodes exist, when they scale, how networking between services works, and you’re the one who gets paged when a node runs out of memory at 3am.

Both run the same application code most of the time. The difference is who owns the layer underneath it, and that ownership is the whole decision.

The variable that decides it for most teams: traffic shape

Ignore the marketing angle for a moment and look at your own traffic graph. Two shapes cover most small products:

  • Bursty and irregular. A handful of requests overnight, a spike at 9am when everyone opens the app, near-silence at weekends. This is common for internal tools, B2B SaaS with office-hours usage, webhook processors, and anything driven by scheduled jobs rather than constant human traffic.
  • Steady and high. A consumer product with continuous global usage, a real-time service that needs to hold state or long-lived connections (chat, video, streaming telemetry), or a data pipeline running near-constant throughput.
  • Serverless pricing is built around the first shape. You pay for what runs, and idle time costs nothing. Push that same bursty workload onto a Kubernetes cluster and you’re paying for nodes sized to handle the 9am spike, sitting mostly idle the rest of the day, with someone still responsible for patching and capacity planning on infrastructure that’s barely used.

    Flip it around: run a steady, high-utilisation workload on a per-invocation serverless platform and the per-unit cost usually loses to reserved container capacity once you’re sustaining meaningful load. The crossover point depends on your cloud provider’s pricing and your actual utilisation, so it’s worth modelling with your own numbers rather than a rule of thumb, but the direction is consistent across providers.

    Operational load is a cost that doesn’t show up on the invoice

    This is the part that gets underweighted in most comparisons. A container platform, even a managed one, needs someone who understands node pools, upgrade cycles, network policies, and what happens when a pod gets OOM-killed during a deploy. That’s a real skill, it takes time to build, and on a small team it’s usually one person’s part-time responsibility layered on top of their actual job.

    Serverless platforms remove most of that surface. There’s no cluster to upgrade and no capacity to plan for the common case. The trade is that you give up low-level control: you can’t tune the kernel, you’re bound by the platform’s execution limits, and debugging a cold start or a platform-side throttling decision means reading the provider’s documentation rather than your own logs.

    For a team without a dedicated platform engineer, that trade is usually worth making. The question to ask honestly is not “which is more powerful” but “who on this team wants to own a Kubernetes cluster, and what do they stop doing to make time for it.”

    Where serverless breaks down

    • Long-running or stateful work. Most function platforms cap execution time and expect statelessness. Real-time transcription, long video processing jobs, or anything holding a persistent connection tends to fight the platform rather than use it.
    • Cold starts on the latency-sensitive path. If a user is waiting on the response, a few hundred milliseconds of cold-start latency on an infrequently called function is a real user-facing problem, not a rounding error.
    • Sustained high throughput. As covered above, the economics stop favouring you once utilisation is consistently high.
    • Deep provider lock-in you’re not comfortable with. Function platforms are the least portable layer of most clouds. Moving from one provider’s functions to another’s is rarely a lift-and-shift; it’s closer to a rewrite of the integration layer.
    • Where containers earn their overhead

      • Predictable, high-utilisation services. Anything running close to 24/7 at meaningful load, where reserved capacity beats per-invocation pricing.
      • GPU or specialised hardware workloads. Model inference at scale, or anything needing hardware the function platforms don’t expose cleanly.
      • Regulated environments needing infrastructure-level control. Some compliance requirements are easier to demonstrate when you can point to a specific node, network boundary, or audit trail you control directly, rather than a shared multi-tenant platform.
      • Portability as a first-class requirement. If avoiding cloud lock-in is a genuine business constraint, not just a preference, containers travel between providers far more cleanly than functions do.
      • The realistic outcome: both, for different things

        In practice, most products past a certain size end up running a mix: a steady core service on containers because the economics and control justify the ops overhead, alongside a set of bursty, event-driven jobs on serverless because there’s no good reason to keep a cluster warm for something that fires forty times a day. Treating it as an either/or architectural commitment up front tends to produce more regret than treating it as a per-workload decision revisited as the product’s traffic shape becomes clearer.

        The mistake to avoid is choosing containers because it looks like the more serious, scalable option, without anyone on the team actually available to run them. Under-resourced Kubernetes is worse than well-run serverless in almost every practical sense: worse reliability, worse security posture, and worse velocity, because the team is spending time on cluster maintenance instead of the product.

        A checklist before you commit

        • Plot your actual (or realistically projected) traffic over a week. Bursty and irregular points towards serverless; steady and high points towards containers.
        • Name the person who would own cluster operations if you go the container route. If there isn’t one, that’s your answer.
        • Check whether any workload needs long-running execution, persistent connections, or specialised hardware. If so, that workload goes on containers regardless of what the rest of the system uses.
        • Model the cost crossover with your own provider’s pricing at your expected utilisation, not a generic benchmark.
        • Decide how much you actually care about multi-cloud portability, honestly, not aspirationally. It changes the answer for function platforms in particular.
        • Plan to run a mix rather than forcing every workload onto one model. It’s normal, not a sign of an inconsistent architecture.
Filed under: