Deployment Models
VM, container, managed container, function, PaaS and Kubernetes compared by the engineering problem each one is solving, not by the marketing category.
The requirement, the obvious build, and why it breaks
Every lesson starts where the work starts: someone asked for something, and the first implementation that comes to mind has a problem.
What am I actually choosing between when I choose where my backend runs?
The service works on a laptop. It has to run somewhere the team can deploy to on a Tuesday afternoon without a meeting, and keep running when nobody is watching.
Pick whatever the last team used, or whatever the tutorial used. They are all "somewhere the code runs" — the choice is mostly about cost and preference.
The service is deployed to a VM and nobody wrote the process supervisor unit, so the first crash at 2am is permanent until a human logs in.
- The service is deployed to a VM and nobody wrote the process supervisor unit, so the first crash at 2am is permanent until a human logs in.
- The service is deployed as a function, and the first real traffic spike opens one database connection per concurrent invocation until the database refuses new ones (Connection Pools).
- The team adopts Kubernetes for one service, and now owns ingress, certificates, autoscaling policy and node upgrades — a platform team's workload carried by three application engineers.
- A PaaS is chosen for speed, and eighteen months later a requirement it does not support (a private network peering, a long-running consumer, a custom sidecar) has no incremental answer.
What is actually happening
- Every model answers the same five questions, and the only difference is who answers them: who provides the OS, who supplies the language runtime, who restarts the process when it dies, who decides how many copies exist, and who routes traffic to them.
- VM — you get a machine. You own the OS, the runtime, process supervision, log shipping and the deploy mechanism. Maximum control, maximum standing work.
- Container on a VM you manage — the image freezes the filesystem and the runtime, so "works on my machine" becomes literally true; you still own supervision and placement.
- Managed container service — you hand over an image and a desired count. The platform owns placement, restart and rollout; you own the image and everything inside it.
- Function — you hand over a handler. The platform owns the process lifecycle entirely, which means it may create and destroy one per burst of traffic, and your code cannot assume it persists between calls.
- PaaS — you hand over source. The platform builds it, runs it and routes to it. You trade the ability to be unusual for the ability to ship today.
- Kubernetes — you hand over declarative objects describing the desired state, and a control loop works to make reality match. It is not a hosting product; it is a scheduler and an API for building one.
- The models are a spectrum of surrendered control, not a ladder of sophistication. Nothing about a container makes a service better than one on a VM; it makes the deploy reproducible.
The ladder is about who owns the lifecycle
Strip the branding and every deployment model is a line drawn through the same stack. Below the line is somebody else's job; above it is yours. Moving the line up removes work and removes options at exactly the same rate, which is why there is no correct answer independent of the team.
The practically important consequence is the last row. As the line rises, the platform gains the right to start and stop your process whenever it likes — for a rollout, a scale-down, a node drain, an idle timeout. A model where the platform owns the lifecycle is a model where your process must be able to die correctly.
| Model | You own | Platform owns | What the app must do differently |
|---|---|---|---|
| VM | OS, runtime, supervisor, deploy, logs | Hardware, network, disk | Ship a service unit and log rotation; nothing about the code changes. |
| Container (self-run) | Image contents, placement, supervision | Hardware, container runtime | Behave correctly as PID 1: handle signals, log to stdout. |
| Managed containers | Image contents | Placement, restart, rollout, routing | Readiness endpoint and graceful shutdown become load-bearing. |
| PaaS | Source and config | Build, run, route, restart | Accept the platform's build conventions and its port/env contract. |
| Functions | Handler code | Everything else, including process lifetime | No work after the response; connections per invocation; cold start on the critical path. |
| Kubernetes | Images plus the declared desired state | Reconciliation toward that state | Probes, resource requests/limits, termination grace, and no local disk. |
Choosing by the problem, not the category
The useful framing is to name the thing that is currently hard. Almost every model exists because one specific operational problem became intolerable at some organisation, and adopting it inherits both the fix and the assumptions.
If nothing on this list is currently painful, the honest answer is that the simplest model you can deploy to today is correct, and the decision can be revisited when one of these becomes true.
Which operational problem am I buying a solution to?
when Environment drift, missing system libraries, runtime version mismatches.
cost Containerise. You gain a build step and own base-image patching (Containerizing a Backend).
when No supervision, manual recovery, single machine.
cost A managed container service or PaaS. You give up host access and gain a rollout you do not control.
when Webhook receivers, internal tools, event glue.
cost Functions. You gain cold starts, execution limits and connection pressure (Serverless Backends).
when Many teams, shared platform requirements, real multi-tenancy of infrastructure.
cost An orchestrator. You gain an API and a standing operational burden (Running a Backend on Kubernetes).
when A new product with unknown shape and no platform team.
cost PaaS. You gain speed and a ceiling you will meet later, visibly.
when One service, modest traffic, one team.
cost A supervised process on a VM is a real answer. The cost is that it does not impress anyone.
What stays the same in every box
The application obligations do not move as the line rises — they become more strictly enforced. A VM tolerates a service that ignores SIGTERM because the process survives for months. A managed platform terminates instances routinely, so the same defect becomes a stream of failed requests on every deploy and every scale-down.
This is why the deployment module is mostly application work. Four small properties make a backend portable across the entire spectrum, and none of them are platform features.
How to build it
Most important first.
- Start from the workload, not the platform. A long-lived HTTP service, a queue consumer, a nightly batch job and a webhook receiver have genuinely different requirements, and one team can use two models without inconsistency.
- Choose the least platform that meets the requirement you can name today, and write down the requirement that would force a move. "We will need Kubernetes eventually" is not a requirement; "we need per-pod network policy for a compliance audit" is.
- Whatever you choose, the application obligations are the same and must be built first: config from the environment (Configuration: Separating Code From Environment), a real readiness signal (Health Checks: Startup, Readiness, Liveness), graceful shutdown on SIGTERM (Graceful Shutdown), and no durable state in the instance (Stateless Services).
- Push the platform decision behind an image or a process contract. A service that is a container with an entrypoint, env config and stdout logs can move between four of these models without a code change; one that reads a config file from
/etcand writes to local disk cannot. - Count the operational surface you are taking on, not the one you are removing. Every model removes some work and adds some — functions remove supervision and add cold starts, connection pressure and a distributed trace that ends abruptly.
What can go wrong
- Choosing an orchestrator to solve a problem the team did not have, and acquiring an operations backlog that outlives the original service (Scoring Operational Complexity in Cloud & Infrastructure).
- Choosing functions for a workload that holds a database connection per invocation and discovering the ceiling is the database, not the platform (Serverless Backends).
- Choosing a PaaS and then re-implementing half a platform inside it with sidecar hacks, which is worse than either endpoint.
- Mixing models without a shared deploy contract, so every service has its own bespoke release procedure and nobody can deploy anything they did not write.
- Each model moves the patching boundary. On a VM, unpatched OS packages are yours. In a container, the base image is yours even though it feels like the platform's. In a function, the runtime is the platform's and your dependencies are still yours.
- Instance credentials differ in blast radius: a long-lived VM with a broad role is a standing target, while a short-lived workload identity scoped to one action is not. Prefer platform-issued short-lived identity to static keys in environment variables (Secrets Are Not Configuration).
- Anything that lets a build produce the running image is a production write path. Treat the pipeline's credentials as production credentials.
- "Containers are the modern way, so a VM deployment is legacy." A single well-supervised process on a VM is a completely legitimate production deployment, and for one service it is often less total machinery.
- "Kubernetes is how you scale." Kubernetes is how you schedule. Scaling comes from statelessness and capacity; you can have both without it and neither with it.
- "Serverless means no servers to think about." It means no servers to administer. You still think about concurrency, connections, cold start and execution limits — and you think about them more precisely, not less.
- "We should standardise on one model." One model for the deploy *contract* is valuable. One model for every workload is how a nightly batch job ends up as a long-running pod that sleeps 23 hours.
Operating it
- Log to stdout as structured lines in every model; a file on a disk that disappears is not an observability strategy (Structured Logging).
- Record the deployed version — image digest or commit sha — as a label on every metric and a field on every log line, so "did this start at the deploy?" is a query and not a guess (Deploys Are the First Suspect).
- Watch restart counts per instance. In managed models the platform will restart a crashing process forever and the service will look "up" while every request fails.
- At small scale the difference is almost entirely operational effort; every model serves modest traffic fine, and choosing on performance grounds at this size is choosing on noise.
- At 10x the difference is how fast you can add capacity and how safely you can roll back. Models where a deploy is an image and a count scale by editing the count.
- At 100x the difference is what you can express: pinning workloads to hardware, controlling network paths, running sidecars, doing partial rollouts by traffic percentage. That is when a scheduler starts earning its cost.
- Control and convenience trade directly against each other, and the trade is not reversible cheaply in either direction. A PaaS you outgrow costs a migration; an orchestrator you did not need costs continuous attention.
- Reproducibility costs build time. Containers make deploys deterministic and make every dependency upgrade a rebuild.
- Handing lifecycle to the platform means the platform kills your process on its schedule, which is precisely why graceful shutdown stops being optional.
Where this applies
Backend advice is context-sensitive. These labels say what each claim is specific to, and where a different stack or scale would differ.
- GENERALThe ownership question — OS, runtime, supervision, count, routing — is the same everywhere. What differs is who answers each part.
- CLOUD-SPECIFICEvery provider draws the category lines differently: a "managed container service" that runs one request per instance and one that runs hundreds concurrently are sold under the same phrase and behave nothing alike (Mapping Services Across Cloud Providers).
- SIMPLIFIEDSix models presented as distinct; in practice they blend — a managed container platform with scale-to-zero and per-request billing is a function service wearing a container interface.
Where the depth lives
This domain teaches the application-side mechanism and hands the rest off.
- — System Design — sizing a deployment to a traffic profile before any platform is chosen.