JOURNAL

Durable Objects Are the Session, Containers Are the Muscle: Reading Cloudflare’s Streamline

Cloudflare's new reference architecture for video processing splits a Worker, a Durable Object, and a Container into three distinct jobs. The split is the actual lesson — here's what each layer is for and when you'd copy this pattern.

cloudflare streamline durable objects containers

Read with AI

Choose content to copy and paste into your AI assistant. Nothing is sent automatically. CMS content is converted to Markdown; original Markdown is used when available.

Cloudflare published a reference architecture called Streamline on October 2nd. It’s a video pipeline — subtitle burn-in, dynamic livestream overlays, webcam compositing. Three use cases most of us will never personally build. I’d still argue the post is worth reading closely, because the actual thing being demonstrated isn’t video processing. It’s where to draw the line between “state that must be consistent” and “work that must be heavy,” and that line shows up in every long-running workload you’ll ever design for the edge.

Three layers, three jobs, no overlap

The shape is: a Worker out front handling control signaling and Cloudflare Access auth, a Durable Object per active session owning the orchestration logic, and a Container running a Go binary wrapping FFmpeg for the actual encode/decode work. Delivery happens over RTMP, HLS, or WebSocket depending on what’s consuming the stream.

Here’s the part worth sitting with: none of these three layers could absorb another layer’s job without the architecture getting worse.

The Worker can’t hold session state. Workers are stateless by design — each request can land on a different isolate, anywhere in Cloudflare’s network. That’s exactly what you want for routing and auth checks, and exactly wrong for “remember which encoding job is in progress for this specific stream.” If you tried to track session state in a KV store hit from the Worker, you’d be fighting eventual consistency on every single frame-state update.

The Durable Object can’t do the FFmpeg work itself. A Durable Object gives you a single-threaded, strongly consistent execution context — one instance, one location, no race conditions on its own state. That’s perfect for “is this session active, what’s its current encoding config, route this control message to the right place.” It is a terrible place to run a CPU-bound video transcode, because you’d be burning your one consistency guarantee on work that doesn’t need it and blocking the orchestration logic while you do it.

The Container can’t be the source of truth. Containers here are disposable compute — they spin up, do the FFmpeg work, and can be killed and restarted without anyone caring about their internal state. If you put session ownership inside the container, you lose it the moment the container recycles.

So the split isn’t arbitrary. Worker owns identity and routing. Durable Object owns session truth and orchestration. Container owns the expensive, stateless-from-the-outside compute. Each layer is doing the one thing it’s actually built for.

A sketch of what the orchestration logic looks like

Cloudflare doesn’t publish the full source, but the shape of a Durable Object managing a Streamline-style session looks roughly like this:

export class StreamSession {
  state: DurableObjectState;
  containerId: string | null = null;

  async fetch(request: Request) {
    const { type, payload } = await request.json();

    switch (type) {
      case "start":
        this.containerId = await this.spinUpContainer(payload.config);
        await this.state.storage.put("status", "encoding");
        return new Response("started");

      case "update-overlay":
        // single-threaded by DO guarantee — no lost updates
        await this.forwardToContainer(this.containerId, payload);
        return new Response("updated");

      case "stop":
        await this.tearDownContainer(this.containerId);
        await this.state.storage.delete("status");
        return new Response("stopped");
    }
  }
}

The interesting line is update-overlay. A livestream with a dynamic overlay — a score ticker, a lower third, a live viewer count — needs updates applied in order, without two concurrent requests stepping on each other. That’s the exact problem Durable Objects solve by construction: one instance, one event loop, no need to hand-roll a lock. The Container just receives “render this overlay config now” and does the pixel work; it doesn’t need to know anything about ordering guarantees, because the DO already enforced them before the message arrived.

Where this pattern generalizes past video

Strip out FFmpeg and you’ve got a pattern for any workload that’s long-running, stateful at the session level, and heavy at the compute level: multiplayer game session servers, collaborative document editors with a heavy rendering step, IoT device fleets where each device needs an ordered command queue but the actual processing is a separate heavy job, or — closer to what a lot of us actually build — an agent orchestration layer where the Durable Object tracks conversation/task state and a Container runs the actual sandboxed code execution.

The mistake I’ve seen teams make with Durable Objects is treating them as a general-purpose compute primitive once they’ve enjoyed the consistency guarantees. They’re not. The moment your DO’s fetch handler is spending real wall-clock time on CPU-bound work instead of routing and bookkeeping, you’ve smuggled a Container’s job into a layer that’s priced and designed for fast, cheap, orchestration-shaped requests. Streamline is a clean example of resisting that temptation on purpose.

The actual takeaway

If you’re designing anything long-running on Workers, don’t start with “how do I make this stateful.” Start with “which part of this actually needs strong consistency, and which part just needs to run for a while.” Those are almost never the same piece of code, and Cloudflare’s own reference architecture is, in effect, a worked example of keeping them separated instead of reaching for whichever primitive is most convenient in the moment.

Discussion

Comments are reviewed before publication. Your email is kept private.

Add a comment

Name and email are required. Do not include confidential information.

Privacy & data

Loading anti-spam verification…

← Back to allĐọc tiếng Việt