Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/containers/patterns.md
1## Routing Patterns23### Session Affinity (Stateful)45```typescript6export class SessionBackend extends Container {7defaultPort = 3000;8sleepAfter = "30m";9}1011export default {12async fetch(request: Request, env: Env) {13const sessionId = request.headers.get("X-Session-ID") || crypto.randomUUID();14const container = env.SESSION_BACKEND.getByName(sessionId);15await container.startAndWaitForPorts();16return container.fetch(request);17}18};19```2021**Use:** User sessions, WebSocket, stateful games, per-user caching.2223### Load Balancing (Stateless)2425```typescript26export default {27async fetch(request: Request, env: Env) {28const container = env.STATELESS_API.getRandom();29await container.startAndWaitForPorts();30return container.fetch(request);31}32};33```3435**Use:** Stateless HTTP APIs, CPU-intensive work, read-only queries.3637### Singleton Pattern3839```typescript40export default {41async fetch(request: Request, env: Env) {42const container = env.GLOBAL_SERVICE.getByName("singleton");43await container.startAndWaitForPorts();44return container.fetch(request);45}46};47```4849**Use:** Global cache, centralized coordinator, single source of truth.5051## WebSocket Forwarding5253```typescript54export default {55async fetch(request: Request, env: Env) {56if (request.headers.get("Upgrade") === "websocket") {57const sessionId = request.headers.get("X-Session-ID") || crypto.randomUUID();58const container = env.WS_BACKEND.getByName(sessionId);59await container.startAndWaitForPorts();6061// ⚠️ MUST use fetch(), not containerFetch()62return container.fetch(request);63}64return new Response("Not a WebSocket request", { status: 400 });65}66};67```6869**⚠️ Critical:** Always use `fetch()` for WebSocket.7071## Graceful Shutdown7273```typescript74export class GracefulContainer extends Container {75private connections = new Set<WebSocket>();7677onStop() {78// SIGTERM received, 15 minutes until SIGKILL79for (const ws of this.connections) {80ws.close(1001, "Server shutting down");81}82this.ctx.storage.put("shutdown-time", Date.now());83}8485onActivityExpired(): boolean {86return this.connections.size > 0; // Keep alive if connections87}88}89```9091## Concurrent Request Handling9293```typescript94export class SafeContainer extends Container {95private initialized = false;9697async fetch(request: Request) {98await this.ctx.blockConcurrencyWhile(async () => {99if (!this.initialized) {100await this.startAndWaitForPorts();101this.initialized = true;102}103});104return super.fetch(request);105}106}107```108109**Use:** One-time initialization, preventing concurrent startup.110111## Activity Timeout Renewal112113```typescript114export class LongRunningContainer extends Container {115sleepAfter = "5m";116117async processLongJob(data: unknown) {118const interval = setInterval(() => {119this.ctx.storage.put("keepalive", Date.now());120}, 60000);121122try {123await this.doLongWork(data);124} finally {125clearInterval(interval);126}127}128}129```130131**Use:** Long operations exceeding `sleepAfter`.132133## Multiple Port Routing134135```typescript136export class MultiPortContainer extends Container {137requiredPorts = [8080, 8081, 9090];138139async fetch(request: Request) {140const path = new URL(request.url).pathname;141if (path.startsWith("/grpc")) this.switchPort(8081);142else if (path.startsWith("/metrics")) this.switchPort(9090);143return super.fetch(request);144}145}146```147148**Use:** Multi-protocol services (HTTP + gRPC), separate metrics endpoints.149150## Workflow Integration151152```typescript153import { WorkflowEntrypoint } from "cloudflare:workers";154155export class ProcessingWorkflow extends WorkflowEntrypoint {156async run(event, step) {157const container = this.env.PROCESSOR.getByName(event.payload.jobId);158159await step.do("start", async () => {160await container.startAndWaitForPorts();161});162163const result = await step.do("process", async () => {164return container.fetch("/process", {165method: "POST",166body: JSON.stringify(event.payload.data)167}).then(r => r.json());168});169170return result;171}172}173```174175**Use:** Orchestrating multi-step container operations, durable execution.176177## Queue Consumer Integration178179```typescript180export default {181async queue(batch, env) {182for (const msg of batch.messages) {183try {184const container = env.PROCESSOR.getByName(msg.body.jobId);185await container.startAndWaitForPorts();186187const response = await container.fetch("/process", {188method: "POST",189body: JSON.stringify(msg.body)190});191192response.ok ? msg.ack() : msg.retry();193} catch (err) {194console.error("Queue processing error:", err);195msg.retry();196}197}198}199};200```201202**Use:** Asynchronous job processing, batch operations, event-driven execution.203