Topic: Cloudflare
Cloudflare Containers: When a Worker Is Not Enough
Cloudflare Containers is generally available. Here is a practical guide to its cold starts, pricing, ephemeral disks, scaling, and division of labor with Workers.
Animated meme (expand/collapse)
Shipping a Docker image without first adopting Kubernetes is not a new wish. Now that Cloudflare Containers is generally available, the tempting conclusion is that every existing Worker should finally move into a container.
Not so fast.
Containers fill runtime gaps that Workers deliberately leave open: native executables, a full filesystem, more CPU and memory, and existing container images. They are not a new packaging format for Workers, nor do they turn Cloudflare into an ordinary virtual machine host. A more accurate architecture is: use a Worker as the front door and control plane; rent a Container only for work that truly needs Linux.
That distinction determines latency, cost, and how much lifecycle logic you own.
Start with the requirement, not “can it run Docker?”
Cloudflare’s getting-started guide begins with a Worker receiving requests and routing them to one or more Containers. The Container class is built on a Durable Object, which manages routing, lifecycle, and durable state, while the image runs inside a Linux VM.
That is the product model, not an incidental tutorial choice. A useful first boundary looks like this:
| Work | Prefer |
|---|---|
| Authentication, redirects, caching, lightweight APIs, edge routing | Worker |
| FFmpeg, headless browsers, LibreOffice, native CLIs | Container behind a Worker |
| A full Linux runtime or an existing Docker image | Container behind a Worker |
| Durable files or business data | R2, a database, or Durable Object storage |
| A low-traffic but permanently running traditional web app | Benchmark a VPS or another container platform too |
If code already runs well in the Worker runtime, moving it into a Container usually adds an image, cold starts, instance sizing, and disk lifecycle. Familiarity with Docker is not a migration requirement.
The inverse is also true. If you are rewriting native dependencies, removing browser automation, or twisting CPU-heavy work into tiny requests just to fit a Worker, a Container is not a retreat. It puts the workload back in an appropriate execution environment.
The useful shape: Worker in front, Container behind it
The minimal concept looks roughly like this:
import { Container } from "@cloudflare/containers";
export class Backend extends Container {
defaultPort = 8080;
sleepAfter = "10m";
}
export default {
async fetch(request, env) {
return env.BACKEND.getByName("shared").fetch(request);
},
};
The Worker can apply authentication, rate limits, cache keys, and routing before selecting a Container by tenant, session, or job ID. When heavy work completes, its output goes to R2 or a database rather than remaining on the Container’s local disk.
This split has a practical benefit: most requests do not need to wake Linux. Health checks, cache hits, rejected requests, and status reads for completed jobs can finish at the Worker layer. Only paths that need the native runtime pay for a Container.
If a job takes seconds or minutes, I would not keep a browser request open while it runs. A better interface is for the Worker to create a job, immediately return its ID, let the Container process it asynchronously, and expose status separately. Users no longer experience cold-start time directly, while retries and timeouts become explicit.
Scale to zero is not a magic phrase
Official pricing is metered in 10-millisecond increments. CPU is charged for active use, while memory and disk are charged from the provisioned instance size for as long as the instance is running. Charges stop when it sleeps and begin after a request or manual start. Worker usage and the Durable Object behind each Container are billed too.
The cost model is therefore closer to:
Container runtime
+ provisioned memory and disk while awake
+ active CPU
+ network egress
+ Worker and Durable Object usage
That makes sleepAfter a cost setting, not just syntactic convenience. Too long, and a quiet service pays for idle memory and disk. Too short, and more users encounter cold starts. Cloudflare’s FAQ says cold starts are often around one to three seconds, depending on image size, entrypoint work, and other factors.
My default is to begin with a small instance and a short but reasonable sleepAfter, then watch three measurements:
- Cold starts per hour
- p95 job-start latency
- The percentage of awake time with no work
If latency hurts conversion or completion rate, extend the sleep timeout. Do not buy a month of idle resources in advance merely because “warm is faster.”
Animated meme (expand/collapse)
The disk disappears, so place state deliberately
The Containers FAQ is direct: local disk is ephemeral. After an instance sleeps, its next start receives a fresh disk defined by the image.
Local disk is appropriate for:
- Files being unpacked
- Intermediate video-transcoding output
- A cache that can be downloaded or recomputed
It is not appropriate for orders, the only copy of an upload, final job state, or anything that must survive a restart. Durable Object storage can keep state near the instance; large objects generally belong in R2, while relational data belongs in a database. FUSE can mount object storage, but the documentation warns against expecting native SSD performance.
An awake Container is not a durability guarantee either. The platform does not promise that an instance will run continuously for a fixed period; host maintenance can still relocate or restart it. A recoverable job must be able to resume from external state.
General availability does not mean scaling is automatic
Cloudflare announced general availability for Containers and Sandboxes in April 2026, but the current scaling model still requires an understanding of instance IDs.
The Scaling and Routing documentation says Containers currently scale by explicitly obtaining and starting instances with unique IDs; getting a reference alone does not start it. Stateless services can use getRandom across a fixed number of instances, but Cloudflare also says built-in autoscaling and routing are planned for a future release.
That makes the platform a good fit today for:
- A clear identity per tenant or session
- A controlled browser or media-worker pool
- Short-lived batch jobs
- Isolated runtime for AI-generated code
But if the requirement is “a stateless web service that automatically grows from three instances to three hundred without application routing,” do not infer that capability from the Cloudflare name. Put instance mapping, concurrency limits, overload responses, and retries into the design.
My decision rule
I ask these questions in order:
- Can a Worker complete the job directly? If yes, stop there.
- Does it truly require Linux, a native binary, or larger resources? Only then use a Container.
- Can the path absorb a one-to-three-second cold start? If not, make it a job or pay for warm time.
- Can it recover after the instance disappears? If not, move state off local disk first.
- Who owns routing and scaling? Today, the answer still includes your Worker code.
- Did the estimate include the whole bill? Count Containers, egress, Workers, and Durable Objects.
This rule intentionally makes Containers the later option. That is not a statement that Containers are immature. It is recognition that the platform has already optimized Workers for ordinary requests. A full Linux runtime should solve a problem that a Worker genuinely cannot.
Implementation note from this site: this publication stays static
This blog is a useful non-example for Containers. Its Wrangler configuration points assets.directory at ./dist, and its deployment command is pnpm build && wrangler deploy. There is no Container binding, image, native binary, or long-running process in the request path; the runtime job is to serve built HTML, CSS, JavaScript, and media.
That leads to a deliberately small decision:
- Build and content parsing happen before deployment.
- A reader request needs a static document, not Linux work.
- Adding a Container would introduce an instance lifecycle without removing a real constraint.
If a future build needs a native tool, I would first run that tool in CI and publish its output as an asset. A Container becomes relevant only when visitors trigger runtime work that needs Linux, such as a conversion job or isolated code execution.
This is source inspection of this site’s configuration, not a load test or a Container cost benchmark. It proves the current deployment shape, not production latency, traffic, or a future workload.
Conclusion: rent Linux when the work needs Linux
The value of Cloudflare Containers is not that every project can become Docker-shaped. It closes the gap between Workers and traditional container hosting. A system can preserve a fast Worker entry point, global routing, and bindings while giving FFmpeg, browsers, native packages, or an existing image a less restrictive environment.
My deliberately boring default is: Workers handle what every request passes through; Containers handle the small set of work that cannot reasonably run without Linux.
When a Container exists only because Docker feels more familiar than Workers, it is probably an extra operations layer. When it removes a runtime rewrite, performs heavy computation, or isolates untrusted work, it has earned the cold start and the bill.
External references
- Cloudflare Changelog: Containers and Sandboxes are now generally available
- Cloudflare Containers: Getting started
- Cloudflare Containers: Scaling and Routing
- Cloudflare Containers: Pricing
- Cloudflare Containers: Frequently Asked Questions
- daily.dev: Cloudflare Containers are FINALLY here
- Reddit: Would you start building your next project on Cloudflare?