PORTFORK

Back to blog

The Portfork isolation architecture

How Portfork Cloud isolates tenants with a per-account hardware-isolated microVM and isolates your apps from each other with a per-app in-VM sandbox. The boundaries, threat model, and trade-offs.

By
Portfork
Published
Tagged
engineering architecture security

When we say your apps run isolated, we mean a specific thing — and it is two things, not one. There is a real virtualization boundary between you and every other tenant, and there is a kernel-enforced sandbox between your own apps. Neither is a shared container runtime where a kernel bug or a noisy neighbor reaches across the fence by default.

This post is the honest description of how that is achieved: what the boundaries are, what crosses them, what an attacker would have to do to break each one, and which trade-offs we chose along the way.

The isolation invariant

Two sentences. The rest of this post explains them.

Each Portfork Cloud account runs in its own hardware-isolated microVM, so one tenant’s apps can never see or reach another’s. Inside that microVM, each of your apps runs in its own sandbox, so your apps are isolated from each other too.

Two layers, two jobs. The tenant boundary is the microVM — a real guest kernel and a virtualized slice of hardware, one per account. The app boundary is an isolated sandbox inside your microVM. The blast radius of a compromised app is, at worst, that app’s sandbox; it does not reach your other apps, and it certainly does not reach another tenant.

What runs where

Three things make up the picture of a hosted app on Portfork Cloud.

The app artifact. The same artifact you run locally on Open Source Portfork. On Cloud it is scheduled onto your account’s microVM, placed in its own sandbox, given a stable URL, and kept reachable around the clock — even with your computer off.

Your account’s microVM. A hardware-isolated guest with its own kernel, dedicated to your account. It wakes on the first request (lazy-start), serves traffic, and scales to zero when idle (idle-reap). No other tenant is co-located inside it.

The per-app sandbox. Inside your microVM, each app runs in its own sandbox: separate Linux namespaces (pid, mount, ipc, uts, user), an isolated filesystem view so one app cannot see another’s files, a scoped environment that carries only the credentials that app needs, dropped Linux capabilities with no-new-privileges, and a seccomp profile that narrows the syscall surface. This is what keeps your own apps apart from each other while they share your account’s machine.

The control plane. The managed orchestration layer that schedules microVMs, routes URLs, injects secrets, and handles billing and account lifecycle. It is the part of Portfork that is commercial; the runtime inside the microVM is the open-source engine.

The flow, end to end

flowchart LR
    A[Request to<br/>app URL] --> B[(Portfork Cloud<br/>control plane)]
    B -- lazy-start --> C[Your account's<br/>microVM]
    C --> D[Per-app<br/>sandbox]
    D -- serve --> E[Response]
    C -- idle-reap --> F[Scaled to zero]
    style C fill:#1c1e1b,stroke:#484d47,color:#efefe7
    style D fill:#1c1e1b,stroke:#484d47,color:#efefe7

The control plane is the only shared component, and it is deliberately narrow: it schedules and routes. It does not run your code. Your code runs inside your account’s microVM, behind a virtualization boundary, and inside a per-app sandbox within it.

Defense in depth: two boundaries, two threat models

The reason there are two layers is that they defend against different things, and a single layer would do one job well and the other badly.

  • The microVM is the tenant boundary. It is the strong one — a hardware-virtualization line with its own kernel. It exists so that a compromise in your account cannot become a compromise of someone else’s. Cross-tenant isolation does not lean on namespaces; it leans on the VM.
  • The sandbox is the app boundary. Inside your own microVM your apps are co-resident by design — that co-residency is what makes scale-to-zero hosting of many small apps cheap. The sandbox is what keeps that co-residency from meaning your apps can read each other’s files, see each other’s processes, or inherit each other’s secrets.

Put together, a compromised app faces a sandbox first and a VM boundary behind it. That is defense in depth, not a single fence.

Per-tenant separation

Your microVM and the apps inside it are scoped to your account. Separation is enforced by the platform, not by convention:

  • A microVM per account. Your account gets its own guest kernel and its own virtualized hardware. There is no shared interpreter, no shared process tree, and no shared filesystem between tenants.
  • A sandbox per app. Within your microVM, each app’s namespaces and filesystem view are its own. One app cannot enumerate, read, or write another app’s files or processes.
  • Network scoping. An app can reach the network paths you grant it and nothing belonging to another tenant. Cross-tenant reachability is not a setting you can flip on by accident; it does not exist.
  • Account-scoped URLs. Stable URLs route to your microVM only.

Secrets

App secrets are stored encrypted in AWS and injected at runtime — and they are injected per app. Each sandbox receives a scoped environment that carries only the credentials that app needs; node-internal secrets are dropped before your app’s process ever sees them. Secrets are never baked into the artifact and never written to logs. When the app scales to zero, the running secret material goes with it; the next start re-injects from the encrypted store.

You also bring your own model access. An API key or a claude setup-token subscription token is handled as a secret on the same path: encrypted at rest, injected at runtime into that app’s sandbox, never in an artifact or a log. There is no inference markup — the model call goes from your app to your provider directly.

What we did not isolate — and why

A few things are deliberately shared, and we documented each choice.

  • Your apps share your account’s microVM. They do not each get a separate VM. Co-residency inside your own machine is what makes hosting a fleet of small, infrequently used apps affordable — and the per-app sandbox is what makes that co-residency safe. If a single sensitive app needs its own machine, the Dedicated microVM add-on gives that one app its own microVM.
  • The control plane. Scheduling, routing, and billing run as a multi-tenant service. Pushing those into per-tenant infrastructure would multiply operational cost for a marginal security gain — the control plane never executes your code. If your threat model needs the whole data plane in-house, Open Source Portfork runs the same apps on your own hardware.
  • Cold-start timing. A lazy-started app takes longer to answer its first request than a warm one. We accept that an observer can tell a cold start from a warm one. Padding every start to a uniform latency would make the product slower for everyone to hide very little.

The threat model, briefly

What does Portfork’s design protect against?

  • A compromised app reaching another tenant. The microVM boundary stands between your account and everyone else’s. Code execution in your account cannot reach another tenant’s apps or the host.
  • A compromised app reaching your other apps. The per-app sandbox — namespaces, an isolated filesystem view, dropped capabilities, no-new-privileges, and a seccomp profile — keeps one of your apps from reading another’s files, secrets, or processes.
  • A noisy neighbor. Resource contention between tenants is bounded by the microVM allocation. One account’s spike does not starve another’s apps.
  • Secret leakage through artifacts or logs. Secrets are injected at runtime per app, never baked in, never logged.

What does it not protect against?

  • A vulnerability in your own app. Portfork isolates apps from each other and tenants from each other; it does not patch your code. Treat your app the way you treat any internet-facing service.
  • Loss of your own model credentials. You bring your own model access. If your provider key leaks from somewhere outside Portfork, that is outside our boundary.

Why we built it this way

There is a simpler model that many hosts use: pack many tenants into shared containers on a shared kernel and rely on namespaces alone for separation. That model is cheaper to operate. It also means a single kernel bug is a cross-tenant incident.

We took a different path, and split the problem in two. The expensive, strong boundary — a microVM with its own kernel — is spent where it matters most: between tenants. The lighter, kernel-enforced boundary — an isolated sandbox — does the per-app job inside your own machine, where co-residency is what makes the economics work in the first place. Scale-to-zero plus per-account microVMs plus per-app sandboxing is how we host a lot of small, infrequently used apps cheaply and safely. We could not in good conscience ship a host we ourselves would not run untrusted agent-built code on.

So that is the design.

See it for yourself

The runtime that hosts apps locally is the same engine that runs inside the microVM on Cloud, and its source is on GitHub today. If you find a problem, our security disclosure policy is written down and we honour it.

We will publish a longer threat-model document with GA. This post is the headline; the details follow.

Try Portfork today. The Open Source edition is free.

Install in under a minute. Host the apps your agents build, on your own machine or on Portfork Cloud.