Serverless vs containers is primarily a decision about application lifecycle. A function is a unit of work that runs when triggered; a container is a unit of runtime designed to keep an application process and its dependencies together.
Both models can run modern APIs, background jobs, web services, and data-processing workloads. The difference is what your team wants to own. Serverless functions remove most runtime and scaling operations around a bounded task. Containers give you more control over the process, runtime environment, networking behavior, and application lifecycle.
For small teams, the choice should follow workload shape rather than architecture fashion. A webhook that wakes up, validates an event, writes a record, and stops is naturally function-shaped. A web application that listens continuously, maintains several processes, or depends on a custom runtime environment is usually container-shaped.
At Raff, we use this distinction before discussing infrastructure size: a function is a unit of work; a container is a unit of runtime. Once that boundary is clear, the next question—VM, managed container platform, or Kubernetes—becomes much easier.
Serverless and containers solve different deployment problems
Serverless functions and containers overlap, but they begin from different abstractions.
A serverless function normally starts from an event:
HTTP request / cron / storage event / one-off job ↓ Function ↓ Work completes
A container normally starts from an application process:
Container image ↓ Application process starts ↓ Process stays available ↓ Requests / jobs / connections arrive
That creates different operating assumptions.
| Decision area | Serverless function | Containerized application |
|---|---|---|
| Main unit | Bounded invocation or task | Application process/runtime |
| Typical lifecycle | Starts from demand or trigger | Starts and remains running until replaced/stopped |
| Scaling unit | Function instances/invocations | Container replicas |
| Runtime ownership | Platform manages more of it | Team defines more of the runtime environment |
| Best fit | Webhooks, cron, events, short APIs, bounded jobs | Web apps, APIs, workers, daemons, custom runtimes |
| Idle behavior | Often able to scale to zero | Depends on the container platform and deployment model |
| Process control | Limited by function platform | Broad control inside the image/runtime |
| Persistent local process | Usually a poor fit | Natural fit |
Containers are not automatically "always on." Managed container services can scale aggressively and some can scale to zero. Likewise, functions are not limited to tiny scripts. The useful distinction is who owns the process lifecycle and how much runtime control the workload needs.
The separate Serverless Functions vs VMs guide owns the question of functions versus a full server. This guide stays one layer higher: whether the workload itself should behave like an invocation or like an application process.
Functions fit bounded event-driven work
Functions are strongest when the work has a clear trigger, input, output, and completion point.
Common examples include:
- webhook receivers;
- scheduled cleanup jobs;
- object-storage file processing;
- API callbacks;
- notification fan-out;
- form processing;
- image or document transforms;
- periodic data syncs;
- one-off jobs;
- lightweight HTTP endpoints;
- bounded ETL or batch steps.
The important property is not that the code is small. It is that the work can start, complete, and release its execution environment without needing a persistent process to remain alive.
A function-shaped workload looks like this:
Event arrives ↓ Validate input ↓ Perform bounded work ↓ Write durable state/output ↓ Finish
This model reduces idle-runtime ownership. It also makes independent scaling easier because each function can respond to its own trigger pattern rather than sharing one application process with unrelated work.
The trade-off is that the workload must tolerate the function operating model. Durable state should live in a database or object storage. Startup behavior matters. Runtime limits matter. Long-lived local processes, permanent filesystem assumptions, and arbitrary host-level control are usually poor fits.
Use the dedicated Runtime Limits guide when timeout, memory, temporary storage, or concurrency is the main constraint. Use Cold Starts, Concurrency, and Scale-to-Zero when startup latency is the main decision.
Containers fit long-lived application processes
A container is a stronger fit when the process itself needs to stay alive or when the application needs more control over its runtime environment.
Good container candidates include:
- long-running web applications;
- APIs with steady traffic;
- queue workers that remain connected and continuously consume work;
- services with long-lived TCP connections;
- applications with several coordinated processes;
- software that needs custom system packages or binaries;
- reverse proxies and gateways;
- self-hosted applications designed around Docker images;
- services that rely on stable process-level caches;
- workloads with custom networking or filesystem expectations.
The container image packages the application and its dependencies. That creates a portable runtime unit that can run on a developer machine, a VM, a managed container service, or Kubernetes.
This is where containers differ from VMs. A container does not define the infrastructure boundary. It defines the application runtime boundary. The existing Docker vs Virtual Machines guide owns that distinction.
If the workload needs to stay alive because the process itself matters, a container is usually the clearer abstraction.
That does not mean every persistent web service needs Kubernetes. One or several containers can run perfectly well on a VM with Docker Compose. The Kubernetes vs Docker Compose guide owns when that container deployment should become a cluster.
Scaling and startup behavior change the operating model
Serverless functions and containers can both scale horizontally, but the scaling unit is different.
A serverless platform usually creates function execution instances in response to demand. Idle workloads can often scale down aggressively or to zero. New demand may require new execution environments, which introduces the cold-start trade-off.
A container platform normally scales replicas of a long-lived process. The process may have startup time, readiness checks, connection pools, caches, background threads, or other initialization work that exists for the lifetime of the replica.
Compare the two patterns:
Function request → execution environment → result → environment may disappear
Container start process → become ready → serve many requests/jobs → replace later
That difference matters for application design.
A function should not depend on one invocation receiving the next request. A container can intentionally reuse in-process state such as connection pools or caches while the replica remains alive, although important business state still belongs in durable storage.
Burst traffic can favor functions when the workload is easy to parallelize and has meaningful idle periods. Stable traffic can favor containers when the process is continuously useful and the team benefits from keeping initialization, connections, and runtime state ready.
Neither model removes capacity planning. A function that scales rapidly can overload a database or third-party API. A container service can also create too many replicas. The safe ceiling should be based on downstream capacity, not simply on what the compute platform can launch.
Runtime control and portability favor different choices
Containers are often chosen for portability because an OCI/Docker image packages the application environment explicitly.
That is a real advantage. A container can include:
- system libraries;
- language runtime;
- application dependencies;
- binaries;
- startup commands;
- filesystem layout;
- runtime configuration defaults.
This makes containers useful when the application depends on a precise environment.
Serverless portability depends more heavily on the platform's handler model. A proprietary function signature, event object, runtime API, or SDK can make migration harder even when the application logic is small.
Raff Functions is designed around standard handlers such as Python FastAPI/ASGI, Node.js standard HTTP, JavaScript Web Fetch, TypeScript, and Go net/http. Raff also provides a Dockerfile escape hatch for workloads that need a custom runtime.
That creates an important middle ground: needing a custom dependency or language does not automatically mean the team must operate a standalone container service. If the workload is still event-driven and bounded, a Dockerfile-based function can preserve the serverless lifecycle while giving the runtime more flexibility.
For deeper portability design, Portable Serverless Handlers owns provider-specific adapters, handler formats, and migration boundaries.
Cost follows workload shape rather than the label
There is no universal rule that serverless is cheaper than containers or containers are cheaper than serverless.
The cost shape follows utilization.
Serverless is often attractive when the workload is:
- intermittent;
- bursty;
- event-driven;
- idle for meaningful periods;
- independently scalable;
- easy to bound per invocation.
Container capacity can be attractive when the workload is:
- continuously busy;
- predictable;
- long-lived;
- already using most of its allocated CPU and memory;
- easier to operate as one application process or worker pool.
The mistake is comparing a function invocation directly with one container and stopping there. A container needs somewhere to run: a VM, managed container platform, or cluster. That infrastructure may reserve capacity, autoscale, or scale to zero depending on the platform.
Likewise, function cost depends on execution behavior, memory, CPU, optional warm capacity, retries, and concurrency.
The existing Serverless Function Pricing guide owns Raff's exact function meters and calculation examples. Serverless Cost Controls owns spend caps, retries, warm-instance governance, and cost blast radius.
For this decision, use a simpler question:
Does the workload benefit more from releasing runtime capacity when work ends, or from keeping an application process continuously available?
That answer usually predicts the better economic model more reliably than a generic per-request or per-hour comparison.
Raff supports both models without forcing a platform jump
Raff gives teams several ways to place the workload after the lifecycle decision is made.
Use Raff Functions when the workload is event-driven and should run from HTTP, cron, object-storage, or one-off triggers. Current Raff Functions support scale-to-zero, load-based autoscaling, standard handlers, and a Dockerfile escape hatch for custom runtimes. Raff currently publishes execution windows up to 1 hour by default and up to 24 hours on request, so duration alone does not automatically force every background job into a container.
Use Raff VM when the containerized application should run as a long-lived process with full server access. A VM can host Docker or Docker Compose while the team keeps control over the operating system, container runtime, networking, and persistent services.
Use Raff Kubernetes when container workloads genuinely need cluster-level scheduling, multiple node pools, independent scaling, service discovery, and orchestration across nodes. Kubernetes is a later operating-model decision, not a requirement simply because the application uses containers.
A small-team architecture can therefore mix the models:
Customer-facing containerized app ↓ Raff VM or Kubernetes Event-driven work ↓ Raff Functions Durable data ↓ Managed Database / Object Storage
At Raff, this mixed model is often more useful than forcing every workload into one compute abstraction. The main application can remain a container while webhooks, cron jobs, file transforms, and bursty background tasks run as functions.
