A class for interacting with Containers on Cloudflare Workers.
- HTTP request proxying and WebSocket forwarding
- Simple container lifecycle management (starting and stopping containers)
- Event hooks for container lifecycle events (onStart, onStop, onError)
- Configurable sleep timeout that renews on requests
- Load balancing utilities
npm install @cloudflare/containersimport{Container,getRandom}from'@cloudflare/containers';exportclassMyContainerextendsContainer{// Configure default port for the containerdefaultPort=8080;sleepAfter="1m";}exportdefault{asyncfetch(request,env){constpathname=newURL(request.url).pathname;// If you want to route requests to a specific container,// pass a unique container identifier to .get()if(pathname.startsWith("/specific/")){// In this case, each unique pathname will spawn a new containerletid=env.MY_CONTAINER.idFromName(pathname);letstub=env.MY_CONTAINER.get(id);returnawaitstub.fetch(request);}// (Note: getRandom is a temporary method until built-in autoscaling an// load balancing are added)// If you want to route to one of many containers (in this case 5),// use the getRandom helperletcontainer=awaitgetRandom(env.MY_CONTAINER,5);returnawaitcontainer.fetch(request);},};The main class that extends a container-enbled Durable Object to provide additional container-specific functionality.
defaultPort?: Optional default port to use when communicating with the container. If not set, you must specify port in containerFetch callsrequiredPorts?: Array of ports that should be checked for availability during container startup. Used by startAndWaitForPorts when no specific ports are provided.sleepAfter: How long to keep the container alive without activity (format: number for seconds, or string like "5m", "30s", "1h")manualStart: If true, container won't start automatically on DO start (default: false). Set as a class property or via constructor options.env: Environment variables to pass to the container (Record<string, string>)entrypoint?: Custom entrypoint to override container default (string[])enableInternet: Whether to enable internet access for the container (boolean, default: true)- Lifecycle methods:
onStart,onStop,onError
constructor(ctx: any,env: Env,options?: {defaultPort?: number;// Override default port
sleepAfter?: string|number;// Override sleep timeout
manualStart?: boolean;// Disable automatic container start (preferred way)
explicitContainerStart?: boolean;// Legacy option, use manualStart instead
env?: Record<string,string>;// Environment variables to pass to the container
entrypoint?: string[];// Custom entrypoint to override container default
enableInternet?: boolean;// Whether to enable internet access for the container})onStart(): Called when container starts successfully - override to add custom behavioronStop(): Called when container shuts down - override to add custom behavioronError(error): Called when container encounters an error - override to add custom behavior
fetch(request): Default handler to forward HTTP requests to the container. Can be overridden.containerFetch(...): Sends an HTTP or WebSocket request to the container. Supports both standard fetch API signatures:containerFetch(request, port?): Traditional signature with Request objectcontainerFetch(url, init?, port?): Standard fetch-like signature with URL string/object and RequestInit options Either port parameter or defaultPort must be specified. Automatically detects WebSocket upgrade requests.
start(): Starts the container if it's not running and sets up monitoring, without waiting for any ports to be ready.startAndWaitForPorts(ports?, maxTries?): Starts the container usingstart()and then waits for specified ports to be ready. If no ports are specified, usesrequiredPortsordefaultPort. If no ports can be determined, just starts the container without port checks.stop(reason?): Stops the containerrenewActivityTimeout(): Manually renews the container activity timeout (extends container lifetime)stopDueToInactivity(): Called automatically when the container times out due to inactivity
getRandom(binding, instances?): Load balances requests across multiple container instances
import{Container}from'@cloudflare/containers';exportclassMyContainerextendsContainer{// Configure default port for the containerdefaultPort=8080;// Set how long the container should stay active without requests// Supported formats: "10m" (minutes), "30s" (seconds), "1h" (hours), or a number (seconds)sleepAfter="10m";// Lifecycle method called when container startsoverrideonStart(): void{console.log('Container started!');}// Lifecycle method called when container shuts downoverrideonStop(): void{console.log('Container stopped!');}// Lifecycle method called on errorsoverrideonError(error: unknown): any{console.error('Container error:',error);throwerror;}// Custom method that will extend the container's lifetimeasyncperformBackgroundTask(): Promise<void>{// Do some work...// Renew the container's activity timeoutawaitthis.renewActivityTimeout();console.log('Container activity timeout extended');}// Handle incoming requestsasyncfetch(request: Request): Promise<Response>{// Default implementation forwards requests to the container// This will automatically renew the activity timeoutreturnawaitthis.containerFetch(request);}// Additional methods can be implemented as needed}The Container class automatically supports proxying WebSocket connections to your container. WebSocket connections are bi-directionally proxied, with messages forwarded in both directions. The Container also automatically renews the activity timeout when WebSocket messages are sent or received.
You can call the containerFetch method directly to establish WebSocket connections:
// Connect to a WebSocket on port 9000constresponse=awaitcontainer.containerFetch(request,9000);By default fetch also will do this by calling containerFetch.
You can configure how the container starts by setting the instance properties for environment variables, entrypoint, and network access:
import{Container}from'@cloudflare/containers';exportclassConfiguredContainerextendsContainer{// Default port for the containerdefaultPort=9000;// Set the timeout for sleeping the container after inactivitysleepAfter="2h";// Environment variables to pass to the containerenvVars={NODE_ENV: 'production',LOG_LEVEL: 'info',APP_PORT: '9000'};// Custom entrypoint to run in the containerentrypoint=['node','server.js','--config','production.json'];// Enable internet access for the containerenableInternet=true;// These configuration properties will be used automatically// when the container starts}For more control over container lifecycle, you can use the explicitContainerStart option to disable automatic container startup:
import{Container}from'@cloudflare/containers';exportclassManualStartContainerextendsContainer{// Configure default port for the containerdefaultPort=8080;// Specify multiple required ports that must be ready before the container is considered started// if this is not specified, by default, you will wait only defaultPortrequiredPorts=[8080,9090,3000];// Disable automatic container startupmanualStart=true;constructor(ctx: any,env: any){// You can also set explicitContainerStart via constructor options// super(ctx, env, {// explicitContainerStart: true// });super(ctx,env);}/** * Handle incoming requests - start the container on demand */asyncfetch(request: Request): Promise<Response>{consturl=newURL(request.url);// Start the container if it's not already runningif(!this.ctx.container.running){try{// Handle different startup pathsif(url.pathname==='/start'){// Just start the container without waiting for any portsawaitthis.start();returnnewResponse('Container started but ports not yet verified!');}elseif(url.pathname==='/start-api'){// Only wait for the API port (3000)awaitthis.startAndWaitForPorts(3000);returnnewResponse('API port is ready!');}elseif(url.pathname==='/start-all'){// Wait for all required ports (uses requiredPorts property)awaitthis.startAndWaitForPorts();returnnewResponse('All container ports are ready!');}else{// For other paths, just wait for the default portawaitthis.startAndWaitForPorts(this.defaultPort);}}catch(error){returnnewResponse(`Failed to start container: ${error}`,{status: 500});}}// For all other requests, forward to the containerreturnawaitthis.containerFetch(request);}}You can create a container that doesn't use a default port and instead routes traffic to different ports based on request path or other factors:
import{Container}from'@cloudflare/containers';exportclassMultiPortContainerextendsContainer{// No defaultPort defined - we'll handle port specification manuallyconstructor(ctx: any,env: any){super(ctx,env);}/** * Process an incoming request and route to different ports based on path */asyncfetch(request: Request): Promise<Response>{consturl=newURL(request.url);try{if(url.pathname.startsWith('/api')){// API server runs on port 3000returnawaitthis.containerFetch(request,3000);}elseif(url.pathname.startsWith('/admin')){// Admin interface runs on port 8080returnawaitthis.containerFetch(request,8080);}else{// Public website runs on port 80returnawaitthis.containerFetch(request,80);}}catch(error){returnnewResponse(`Error: ${errorinstanceofError ? error.message : String(error)}`,{status: 500});}}}You can use the containerFetch method with standard fetch API syntax:
import{Container}from'@cloudflare/containers';exportclassFetchStyleContainerextendsContainer{defaultPort=8080;asynccustomHandler(): Promise<Response>{try{// Using the new fetch-style syntaxconstresponse=awaitthis.containerFetch('/api/data',{method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({query: 'example'})});// You can also specify a port with this syntaxconstadminResponse=awaitthis.containerFetch('https://example.com/admin',{method: 'GET'},3000// port);returnresponse;}catch(error){returnnewResponse(`Error: ${errorinstanceofError ? error.message : String(error)}`,{status: 500});}}}The Container class includes an automatic idle timeout feature that will shut down the container after a period of inactivity. This helps save resources when containers are not in use.
import{Container}from'@cloudflare/containers';exportclassTimeoutContainerextendsContainer{// Configure default port for the containerdefaultPort=8080;// Set timeout to 30 minutes of inactivitysleepAfter="30m";// Supports "30s", "5m", "1h" formats, or a number in seconds// Custom method that will extend the container's lifetimeasyncperformBackgroundTask(data: any): Promise<void>{console.log('Performing background task...');// Manually renew the activity timeout, even though// you have not made a request to the containerawaitthis.renewActivityTimeout();console.log('Container activity timeout renewed');}// Activity timeout is automatically renewed on fetch requestsasyncfetch(request: Request): Promise<Response>{consturl=newURL(request.url);// Example endpoint to trigger background taskif(url.pathname==='/task'){awaitthis.performBackgroundTask();returnnewResponse(JSON.stringify({success: true,message: 'Background task executed',nextStop: `Container will shut down after ${this.sleepAfter} of inactivity`}),{headers: {'Content-Type': 'application/json'}});}// For all other requests, forward to the container// This will automatically renew the activity timeoutreturnthis.containerFetch(request);}}This package includes a getRandom helper which routes requests to one of N instances.
In the future, this will be automatically handled with smart by Cloudflare Containers
with autoscaling set to true, but is not yet implemented.
import{Container,getContainer,getRandom}from'@cloudflare/containers';exportclassMyContainerextendsContainer{defaultPort=8080;}exportdefault{asyncfetch(request: Request,env: any){consturl=newURL(request.url);// Example: Load balance across 5 container instancesif(url.pathname==='/api'){constcontainerInstance=awaitgetRandom(env.MY_CONTAINER,5);returncontainerInstance.fetch(request);}// Example: Direct request to a specific containerif(url.pathname.startsWith('/specific/')){constid=url.pathname.split('/')[2]||'default';constcontainerInstance=getContainer(env.MY_CONTAINER,id);returncontainerInstance.fetch(request);}returnnewResponse('Not found',{status: 404});}};This package includes a getContainer helper which returns a container instance
stub.
The first arbument is the Container's Durable Object namespace. The second argument is optional and is a "name" for the Durable Object. This will be used to generate an ID, then return a specific Container instance (Durable Object instance). If no second argument is given, the name "cf-singleton-container" is used.