all posts

The best Cloudflare Containers alternatives in 2026

Ajay Kumar··10 min read

Cloudflare Containers exist to solve a specific problem: Workers are a superb place to run request-shaped JavaScript and a terrible place to run ffmpeg, a headless browser, a Python data script, or anything else that expects a filesystem and a process table. Containers give a Worker a way to hand that work to a real machine.

The design follows from that origin, and it is unlike other container platforms in ways that matter more than the feature list suggests. This guide covers the shape, the places it stops fitting, and what to use instead when it does.

Cloudflare Containers moved fast after launch and the specific limits — instance sizes, concurrency ceilings, regional behaviour — have changed more than once. Treat any number you read anywhere, including here, as a prompt to check the current docs rather than as a fact.

The shape, and why it is unusual

Three properties define the model.

  • No direct ingress. A container does not get a public hostname. Requests arrive at a Worker, and the Worker forwards them to a container it controls. Everything is mediated.
  • A Durable Object is the control plane. You extend a Container class, and lifecycle — start, sleep after idle, stop — is code you write rather than dashboard configuration. This is genuinely powerful for per-user or per-session instances, and genuinely strange if you expected a service definition.
  • Sizes come from a short list. You pick an instance type rather than specifying arbitrary CPU and memory. Fine when your workload fits; a hard wall when it does not.

The upside of all this is that per-session isolation is natural. One Durable Object per user, one container behind it, sleeping when idle — that is a pattern most platforms make you build yourself. If your workload is shaped like that, Containers are a good fit and the alternatives below are mostly worse.

Where it stops fitting

Four situations come up repeatedly.

Your workload needs more memory or CPU than the largest instance type. Video encoding, large builds, in-memory analytics, a database. There is no negotiating with a fixed size list — you either fit or you do not.

You need a plain public service. If the thing you are deploying is a normal web app that should answer on a hostname, routing everything through a Worker is a layer of indirection you are paying for without a reason.

You are running untrusted code and want a hypervisor boundary. Containers share the host kernel, and while Cloudflare's own isolation is not something to hand-wave about, the security argument for microVMs is that a kernel bug is not automatically a tenant boundary bug. If your threat model includes attacker-controlled code, that distinction is the whole question.

You need durable local state. The model assumes containers are disposable and can be replaced; anything you must keep belongs in R2, D1, or the Durable Object's own storage. That is a clean design, but it means a workload built around a local data directory needs rethinking rather than porting.

The alternatives

  • Fly Machines — The closest analogue: a REST API that starts a VM in seconds, stops it when idle, and lets you keep volumes attached. Arbitrary sizes, real ingress, and a programmatic model that supports per-user instances without the Durable Object indirection. The usual first stop for people who outgrow the size list.
  • AWS Fargate — Containers without managing nodes, sized however you like, inside your existing VPC and IAM. Start-up is measured in tens of seconds rather than under one, so it fits background and batch work far better than per-request work.
  • Google Cloud Run — Request-driven containers with scale-to-zero and generous size limits, plus a straightforward public URL. The most direct answer if what you wanted was Containers but with normal ingress and no Worker in front.
  • Modal — Python-first, built for batch and ML workloads, with fast starts and GPUs. A different product rather than a competitor, and clearly better if your workload is a data or inference pipeline.
  • Railway or Render — If you were only using Containers to run an ordinary service that Workers could not host, these are the boring correct answer. Git push, a long-running process, a hostname, done.
  • Cloudflare's own Sandbox SDK — Worth naming, because if your use case is running untrusted or agent-generated code specifically, this sits on top of Containers and gives you an execution-oriented API rather than a lifecycle one. Same isolation properties, less plumbing.
  • PandaStack — Firecracker microVMs with a create-and-destroy API: each workload gets its own kernel rather than a shared one, boots from a snapshot in the low hundreds of milliseconds, and can be forked copy-on-write from a running state. Apps get a real public hostname without a proxy in front, and managed Postgres sits alongside. Billing is metered on active CPU and working-set memory. Best when the workload is untrusted, needs more than a fixed size list allows, or wants per-session machines with a hypervisor boundary; not the pick if you want your compute inside Cloudflare's network next to R2 and D1.

If you liked the per-session pattern, keep it

The genuinely good idea in the Containers model is a machine per user or per session, created on demand and destroyed when idle. It gives you clean isolation, trivial cleanup, and a cost profile that follows actual use. Do not abandon it just because you are changing platforms.

The pattern ports to any platform with a fast create-and-destroy API. What changes is where the lifecycle logic lives: in a Durable Object on Cloudflare, in your own application code elsewhere. Keep the session identifier as the key, keep a time-to-live so nothing leaks, and reconcile orphans on a schedule.

// A machine per session, without a Durable Object holding the lifecycle.
import { Sandbox } from "@pandastack/sdk";

async function forSession(sessionId: string) {
  const existing = sessions.get(sessionId);
  if (existing) return existing;

  const sb = await Sandbox.create({
    template: "code-interpreter",
    ttlSeconds: 900,                 // the platform reaps it if you crash
    metadata: { sessionId },         // so orphans are findable later
  });
  sessions.set(sessionId, sb);
  return sb;
}

// Two rules that make this safe on any platform:
//   1. always set a TTL -- your cleanup code will fail one day
//   2. tag with the session id -- so a sweep can find what leaked

Pick by situation

  • You are already deep in Workers, R2, and D1, and the sizes fit → stay. The integration is the value and nothing else matches it.
  • You outgrew the instance sizes → Fly Machines, Cloud Run, or Fargate, depending on how fast you need starts.
  • You wanted a normal public service → Cloud Run, Railway, or Render. Drop the Worker indirection.
  • You are executing untrusted or model-generated code → a microVM platform, for the kernel boundary rather than the performance.
  • You need per-session machines with local state → Fly Machines with volumes, or PandaStack with persistent sandboxes.
  • Batch, data, or ML work → Modal or Fargate. This was never the Containers use case.

The short version

Cloudflare Containers are best understood as an escape hatch for Workers, not as a general container platform, and they are good at being that. If your compute genuinely belongs next to the rest of your Cloudflare stack, the constraints are a fair price.

If you find yourself fighting the size list, the missing ingress, or the shared-kernel boundary, those are structural rather than temporary, and the move is to a platform whose primary product is compute. Keep the per-session pattern when you go — it was the best part.

Frequently asked questions

Can a Cloudflare Container have its own public URL?

Not directly, and this is deliberate rather than an oversight. The model routes all traffic through a Worker, which forwards requests to a container instance it controls, so the Worker is where authentication, routing, and rate limiting live and the container is never addressable from outside. For workloads that are genuinely an implementation detail behind an API this is a reasonable design and arguably safer than exposing the service. It becomes friction when the thing you are running is an ordinary web application that should simply answer on a hostname, because you are then writing and maintaining a proxy Worker whose only job is to forward requests unchanged. If that is your situation, a platform that gives services real ingress removes a layer rather than adding a feature.

Are Cloudflare Containers safe for running untrusted code?

They are much safer than running it in-process, and the honest caveat is the isolation boundary. Containers share the host kernel, so tenant separation depends on kernel namespaces, cgroups, and seccomp filtering being correct, and a kernel vulnerability is in principle a path between workloads. That is the same trade every container platform makes and it is not a reason for alarm on its own — Cloudflare operates this seriously and most people running untrusted code on containers have no incident. But if attacker-controlled code is the actual product rather than an edge case, the argument for microVMs is specific: each workload gets its own kernel, so the escape has to defeat a hypervisor rather than a shared kernel. Choose based on whether untrusted execution is incidental or central. Cloudflare's Sandbox SDK is also worth looking at, since it gives an execution-shaped API over the same substrate.

How do Cloudflare Containers compare to Fly Machines?

They solve overlapping problems with opposite defaults. Both give you a real machine you can start on demand and stop when idle, and both support the per-user-instance pattern that makes them interesting. Cloudflare's version is mediated: no public ingress, a Durable Object as the control plane, instance types from a fixed list, and tight integration with R2, D1, and the rest of the Workers platform. Fly Machines are addressable, arbitrarily sized, driven by a plain REST API, and support attached volumes for durable local state. Cloudflare is the better fit when your compute is an extension of a Worker and the data it touches already lives in Cloudflare's storage; Fly is the better fit when the machine is the product, when you need sizes outside the list, or when a workload needs to keep data on local disk between runs.

What happens to data written inside a Cloudflare Container?

Treat it as scratch space that disappears. The model assumes instances are disposable and replaceable — they sleep after idle, they can be stopped and recreated, and nothing guarantees the next request reaches the same one — so anything written to the container's filesystem is safe only for the duration of the work in front of you. Durable state belongs in R2 for objects, D1 for relational data, or the Durable Object's own storage for small per-session state. This is a clean design and it is how most people should build anyway, but it means a workload architected around a local data directory, an embedded database file, or a growing cache on disk needs to be restructured rather than lifted across. If that restructuring is not worth it, look at platforms with attachable persistent volumes instead.

Is there a way to keep the per-session container pattern on another platform?

Yes, and it is worth keeping — a machine per user or per session with a short idle timeout gives you clean isolation, trivial cleanup, and costs that follow real use. What Cloudflare provides is somewhere obvious for the lifecycle logic to live, in the Durable Object; elsewhere that logic lives in your own application. The pattern needs three things from a platform: creation fast enough to sit in a request path, a programmatic destroy, and a server-side time-to-live so instances disappear even if your cleanup code never runs. That last one is the part people skip and then regret, because eventually a process crashes between creating an instance and recording it. Tag each instance with its session identifier as well, so a periodic sweep can find and reap anything your bookkeeping lost track of.

Keep reading

Run code in a microVM in one API call.

49ms p50 cold start. Fork, snapshot, and scale to zero.

Start free
Written by Ajay Kumar, Founder, PandaStack.