Skip to main content

Northflank Killed Its microVM Init Container: What One Deleted Startup Step Reveals About PaaS Isolation Overhead

9 min readDora NodaDora Noda
Share
On this page

Buried in Northflank's March & April 2026 changelog, under the modest heading "Faster pod startup," sits a one-sentence change worth more attention than the whole rest of the page: "For PaaS and BYOC clusters running with Northflank microVM secure runtime, the init container is no longer used by default." One per-pod startup step, deleted. No migration guide, no replacement component, no fanfare — which tells you everything about what that step was. It was scaffolding: work the platform did on every single pod start purely so strangers' workloads could share the same machines safely. And now the isolation boundary has moved somewhere cheaper. The verdict up front:

Northflank beforeNorthflank afterA self-hosted CAPI fleet
Where isolation is paid forEvery pod start (init container)Runtime / node setup (amortized)Once per node pool (RuntimeClass + taints)
Changelog scopePaaS + BYOC clusters, microVM runtime onlyYour own pools, your own choice
What was savedNo published figure; estimated tens-to-hundreds of ms per cold start (math below)Per-pod tax never existed
The real lessonPer-workload isolation scaffolding compounds at scale-to-zeroDraw the boundary at the machine, not the container

Note the precision the changelog forces on us: not "deleted everywhere," but no longer used by default on PaaS and BYOC clusters running the microVM secure runtime. That scope is the whole story. The rest of this post is what that step was doing, what removing it plausibly saved, and why a platform that owns its machines never pays this particular tax at all.

What a per-pod isolation step actually does

Northflank has not published what its init container did internally, so what follows is explicitly a reconstruction — but a grounded one, because a per-pod setup step on a multi-tenant microVM platform has a short list of possible jobs, and each one exists for the same reason: tenants don't trust each other, so the platform can't do the work once and share the result. Each duty below is tagged for what it is.

Isolation-only: guest-side identity and policy wiring. On a hosted multi-tenant cluster, every pod needs its tenant's network policy, secret mounts, and workload identity attached before the app starts — and none of it can be baked into a shared image or a shared volume, because the pod next door belongs to a different customer. A setup step that runs per pod, inside the tenant's own microVM boundary, is the natural place to do it. Single-tenant clusters do this once per deployment; multi-tenant platforms do it per start.

Isolation-only: volume ownership and permission fixups. When persistent storage is provisioned centrally and attached to whoever scheduled onto it, somebody has to chown the mount to the tenant's UID before the app touches it — the classic init-container job (chmod/chown on the data dir). On shared infrastructure this can't be a node-level default, because the "correct" owner differs per tenant per pod. Tellingly, the same changelog entry also notes "Builds with volumes now start faster" — a second startup path with the same per-attachment setup cost, optimized in the same release.

Generic (not isolation): app-level seeding and migrations. Init containers also do ordinary work — downloading configs, running schema migrations, waiting on dependencies. That's real startup time, but it isn't an isolation tax: it survives no matter where you draw the boundary, and removing the default init container only helps here if your pods weren't using it for app work in the first place.

The honest summary: the deletable part — the part a platform change can remove without touching your app — is the isolation-only scaffolding, roughly the first two duties. That is the tax multi-tenancy levies per pod start, and it is the tax Northflank just moved elsewhere.

The cold-start budget: where the deleted step sat

Here is the required honesty before the numbers: Northflank published no saved-milliseconds figure. The changelog says "faster," not "47ms faster." So instead of a single flattering number, here is the full budget with ranges, split by the variable that dominates everything else — whether the image was already on the node:

Startup stageCache hit (image on node)Cache miss (image pull)
Scheduling + API overhead~10–50ms~10–50ms
microVM boot (Kata + Firecracker / Cloud Hypervisor)~125–200ms~125–200ms
gVisor sandbox setup (lighter workloads)~10–50ms~10–50ms
Removed init container step (image pull for its image + execution)tens of ms (cached)potentially seconds (own image pull)
App container start + readinessworkload-dependentworkload-dependent
Image pull itself0seconds to minutes (GB-scale images, cold nodes)

The boot-backend numbers are community-measured orders of magnitude, not vendor claims: Kata on Firecracker lands around 125ms, on Cloud Hypervisor around 200ms, on QEMU around 500ms and up; gVisor adds roughly 10–50ms of startup in exchange for 2–10x per-syscall cost later. Against that, Northflank's own published sandbox figure — ~97ms median time-to-interactive, 167ms at burst, per the July 2026 ComputeSDK comparison — reads as a warm-cache, prewarmed-pool number: the runtime is fast after the platform has already paid the cold costs on your behalf.

Estimation method, stated plainly so you can redo it: an init container costs its own image pull (zero if cached and tiny, dominant if not) plus its execution time (a chown over a large volume can take seconds; a policy-file drop takes milliseconds). Deleting the default therefore saves tens of milliseconds in the best case and seconds in the worst — and the worst case is the common one at scale-to-zero, where every start is a cold start on a fresh node and nothing is cached. That asymmetry is exactly why the change shipped under "faster pod startup" with no number attached: the saving isn't one number, it's a distribution, and the tail is where it matters.

The other way to draw the boundary: isolate the pool, not the pod

A self-hosted platform on its own machines can refuse the entire tradeoff, because Kubernetes already has a mechanism for paying the isolation cost once per pool instead of once per pod: RuntimeClass plus node selection.

The shape is simple. Untrusted or tenant workloads get scheduled onto dedicated node pools — via taints, tolerations, and node affinity — where the whole pool runs the hardened runtime (Kata Containers for hardware-VM isolation, gVisor for syscall interception), while trusted system workloads stay on plain runc pools. The isolation cost is then:

  • Paid at pool provisioning time, when the machine joins — not on every pod start. No per-pod setup step exists to delete because there is nothing per-pod to set up; the node already enforces the boundary before the first pod lands.
  • Accounted to the scheduler declaratively. Kata's per-pod memory overhead (guest kernel plus VMM, roughly 130–160 MiB) is declared via overhead.podFixed on the RuntimeClass, so bin-packing math stays honest without a line of platform code.
  • Chosen per workload, not per cluster. RuntimeClass is a per-pod field, so one cluster serves both shapes — which is precisely the flexibility a hosted PaaS has to reimplement with per-pod scaffolding, because its tenants share every node by definition.

This is what a machine-level boundary buys. A hosted multi-tenant PaaS cannot do this: its isolation boundary must wrap each workload, because the node underneath is shared with strangers. A self-hosted fleet can, because it owns the node and can dedicate it. Northflank's engineers shaving a per-pod step and a platform team isolating a pool are solving the same problem from opposite sides of that ownership line — and the ownership side gets to solve it once.

When per-pod milliseconds are the wrong problem — and when they're everything

Mandatory sensitivity analysis, because startup-latency discourse lies by omission exactly the way cost discourse does. Whether the deleted init container mattered depends entirely on the workload's start frequency:

  • Steady-state services (API, worker, database, always on): pods start on deploys and reschedules — a few times a day. Saving even a full second per start amortizes to statistical zero against months of uptime. If this is your fleet, the changelog line is trivia.
  • Preview environments and CI builds (dozens of starts per PR): the per-start tax multiplies by team activity. Northflank optimizing volume-backed build startup in the same release says they see this segment clearly.
  • Scale-to-zero agent sandboxes (thousands of cold starts per hour): now per-start milliseconds are the product. Northflank's headline sandbox result — 100,000 concurrent sandboxes live in 24 seconds from a cold start, zero failures — is only reachable if every per-start step has been interrogated, because at that volume a 200ms scaffolding step isn't latency, it's 5.5 machine-hours of pure overhead per burst.

And the honest counter-case: for Northflank, deleting the default init container was probably harder than isolating a node pool. They can't dedicate machines per tenant without destroying the bin-packing density their pricing assumes — their published meter charges per vCPU-hour precisely because shared nodes are the business model. Moving isolation setup out of the pod-start path while keeping the nodes shared is surgery; dedicating a pool is carpentry. The difficulty ranking flips with the ownership model, which is why "just do what Northflank did" is lane-confusion for a team that owns its hardware.

The takeaway

One deleted default init container, read properly, is a map of the whole PaaS isolation economy: multi-tenant platforms pay for trust per pod start, self-hosted fleets pay per pool, and scale-to-zero workloads are where the two price schedules diverge by orders of magnitude. Know which schedule you're on before you optimize — shaving a step you pay once a day is theater; shaving one you pay 100,000 times per burst is the business.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, where the isolation boundary sits at hardware you control, not scaffolding on every pod start. Star the repo on GitHub or deploy your first app today.

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex