---title: Containers (Beta)description: Run code written in any programming language, built for any runtime, as part of apps built on Workers.image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Containers (Beta)Enhance your Workers with serverless containers Available on Workers Paid planRun code written in any programming language, built for any runtime, as part of apps built on [Workers](https://backiee.wasmer.app/https_developers_cloudflare_com/workers).Deploy your container image to Region:Earth without worrying about managing infrastructure - just define your Worker and [wrangler deploy](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/commands/general/#deploy).With Containers you can run:* Resource-intensive applications that require CPU cores running in parallel, large amounts of memory or disk space* Applications and libraries that require a full filesystem, specific runtime, or Linux-like environment* Existing applications and tools that have been distributed as container imagesContainer instances are spun up on-demand and controlled by code you write in your [Worker](https://backiee.wasmer.app/https_developers_cloudflare_com/workers). Instead of chaining together API calls or writing Kubernetes operators, you just write JavaScript:* [ Worker Code ](#tab-panel-4073)* [ Worker Config ](#tab-panel-4074)JavaScript```import { Container, getContainer } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 4000; // Port the container is listening on sleepAfter = "10m"; // Stop the instance if requests not sent for 10 minutes}export default { async fetch(request, env) { const { "session-id": sessionId } = await request.json(); // Get the container instance for the given session ID const containerInstance = getContainer(env.MY_CONTAINER, sessionId); // Pass the request to the container instance on its default port return containerInstance.fetch(request); },};```Explain Code* [ wrangler.jsonc ](#tab-panel-4071)* [ wrangler.toml ](#tab-panel-4072)JSONC```{ "name": "container-starter", "main": "src/index.js", // Set this to today's date "compatibility_date": "2026-04-12", "containers": [ { "class_name": "MyContainer", "image": "./Dockerfile", "max_instances": 5 } ], "durable_objects": { "bindings": [ { "class_name": "MyContainer", "name": "MY_CONTAINER" } ] }, "migrations": [ { "new_sqlite_classes": ["MyContainer"], "tag": "v1" } ]}```Explain CodeTOML```name = "container-starter"main = "src/index.js"# Set this to today's datecompatibility_date = "2026-04-12"[[containers]]class_name = "MyContainer"image = "./Dockerfile"max_instances = 5[[durable_objects.bindings]]class_name = "MyContainer"name = "MY_CONTAINER"[[migrations]]new_sqlite_classes = [ "MyContainer" ]tag = "v1"```Explain Code[ Get started ](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/get-started/) [ Containers dashboard ](https://dash.cloudflare.com/?to=/:account/workers/containers)---## Next Steps### Deploy your first ContainerBuild and push an image, call a Container from a Worker, and understand scaling and routing.[ Deploy a Container ](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/get-started/)### Container ExamplesSee examples of how to use a Container with a Worker, including stateless and stateful routing, regional placement, Workflow and Queue integrations, AI-generated code execution, and short-lived workloads.[ See Examples ](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/)---## More resources[Beta Information](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/beta-info/)Learn about the Containers Beta and upcoming features.[Wrangler](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/commands/containers/#containers)Learn more about the commands to develop, build and push images, and deploy containers with Wrangler.[Limits](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/#limits)Learn about what limits Containers have and how to work within them.[SSH](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/ssh/)Connect to running Container instances with SSH through Wrangler.[Containers Discord](https://discord.cloudflare.com)Connect with other users of Containers on Discord. Ask questions, show what you are building, and discuss the platform with other developers.```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}}]}```------title: Getting starteddescription: In this guide, you will deploy a Worker that can make requests to one or more Containers in response to end-user requests.In this example, each container runs a small webserver written in Go.image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/get-started.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Getting startedIn this guide, you will deploy a Worker that can make requests to one or more Containers in response to end-user requests. In this example, each container runs a small webserver written in Go.This example Worker should give you a sense for simple Container use, and provide a starting point for more complex use cases.## Prerequisites### Ensure Docker is running locallyIn this guide, we will build and push a container image alongside your Worker code. By default, this process uses[Docker ↗](https://www.docker.com/) to do so.You must have Docker running locally when you run `wrangler deploy`. For most people, the best way to install Docker is to follow the [docs for installing Docker Desktop ↗](https://docs.docker.com/desktop/). Other tools like [Colima ↗](https://github.com/abiosoft/colima) may also work.You can check that Docker is running properly by running the `docker info` command in your terminal. If Docker is running, the command will succeed. If Docker is not running, the `docker info` command will hang or return an error including the message "Cannot connect to the Docker daemon".## Deploy your first ContainerRun the following command to create and deploy a new Worker with a container, from the starter template: npm yarn pnpm```npm create cloudflare@latest -- --template=cloudflare/templates/containers-template``````yarn create cloudflare --template=cloudflare/templates/containers-template``````pnpm create cloudflare@latest --template=cloudflare/templates/containers-template```When you want to deploy a code change to either the Worker or Container code, you can run the following command using [Wrangler CLI](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/): npm yarn pnpm```npx wrangler deploy``````yarn wrangler deploy``````pnpm wrangler deploy```When you run `wrangler deploy`, the following things happen:* Wrangler builds your container image using Docker.* Wrangler pushes your image to a [Container Image Registry](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/image-management/) that is automatically integrated with your Cloudflare account.* Wrangler deploys your Worker, and configures Cloudflare's network to be ready to spawn instances of your containerThe build and push usually take the longest on the first deploy. Subsequent deploys are faster, because they [reuse cached image layers ↗](https://docs.docker.com/build/cache/).NoteAfter you deploy your Worker for the first time, you will need to wait several minutes until it is ready to receive requests. Unlike Workers, Containers take a few minutes to be provisioned. During this time, requests are sent to the Worker, but calls to the Container will error.### Check deployment statusAfter deploying, run the following command to show a list of containers containers in your Cloudflare account, and their deployment status: npm yarn pnpm```npx wrangler containers list``````yarn wrangler containers list``````pnpm wrangler containers list```And see images deployed to the Cloudflare Registry with the following command: npm yarn pnpm```npx wrangler containers images list``````yarn wrangler containers images list``````pnpm wrangler containers images list```### Make requests to ContainersNow, open the URL for your Worker. It should look something like `https://hello-containers.<YOUR_WORKERS_SUBDOMAIN>.workers.dev`.If you make requests to the paths `/container/1` or `/container/2`, your Worker routes requests to specific containers. Each different path after "/container/" routes to a unique container.If you make requests to `/lb`, you will load balanace requests to one of 3 containers chosen at random.You can confirm this behavior by reading the output of each request.## Understanding the CodeNow that you've deployed your first container, let's explain what is happening in your Worker's code, in your configuration file, in your container's code, and how requests are routed.## Each Container is backed by its own Durable ObjectIncoming requests are initially handled by the Worker, then passed to a container-enabled [Durable Object](https://backiee.wasmer.app/https_developers_cloudflare_com/durable-objects). To simplify and reduce boilerplate code, Cloudflare provides a [Container class ↗](https://github.com/cloudflare/containers) as part of the `@cloudflare/containers` NPM package.You don't have to be familiar with Durable Objects to use Containers, but it may be helpful to understand the basics.Each Durable Object runs alongside an individual container instance, manages starting and stopping it, and can interact with the container through its ports. Containers will likely run near the Worker instance requesting them, but not necessarily. Refer to ["How Locations are Selected"](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/#how-are-locations-are-selected)for details.In a simple app, the Durable Object may just boot the container and proxy requests to it.In a more complex app, having container-enabled Durable Objects allows you to route requests to individual stateful container instances, manage the container lifecycle, pass in custom starting commands and environment variables to containers, run hooks on container status changes, and more.See the [documentation for Durable Object container methods](https://backiee.wasmer.app/https_developers_cloudflare_com/durable-objects/api/container/) and the[Container class repository ↗](https://github.com/cloudflare/containers) for more details.### ConfigurationYour [Wrangler configuration file](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/configuration/) defines the configuration for both your Worker and your container:* [ wrangler.jsonc ](#tab-panel-4123)* [ wrangler.toml ](#tab-panel-4124)JSONC```{ "containers": [ { "max_instances": 10, "class_name": "MyContainer", "image": "./Dockerfile", }, ], "durable_objects": { "bindings": [ { "name": "MY_CONTAINER", "class_name": "MyContainer", }, ], }, "migrations": [ { "tag": "v1", "new_sqlite_classes": ["MyContainer"], }, ],}```Explain CodeTOML```[[containers]]max_instances = 10class_name = "MyContainer"image = "./Dockerfile"[[durable_objects.bindings]]name = "MY_CONTAINER"class_name = "MyContainer"[[migrations]]tag = "v1"new_sqlite_classes = [ "MyContainer" ]```Explain CodeImportant points about this config:* `image` points to a Dockerfile, to a directory containing a Dockerfile, or to a fully qualified image reference such as `registry.cloudflare.com/<YOUR_ACCOUNT_ID>/<IMAGE>:<TAG>`.* `class_name` must be a [Durable Object class name](https://backiee.wasmer.app/https_developers_cloudflare_com/durable-objects/api/base/).* `max_instances` declares the maximum number of simultaneously running container instances that will run.* The Durable Object must use [new\_sqlite\_classes](https://backiee.wasmer.app/https_developers_cloudflare_com/durable-objects/best-practices/access-durable-objects-storage/#create-sqlite-backed-durable-object-class) not `new_classes`.### The Container ImageYour container image must be able to run on the `linux/amd64` architecture, but aside from that, has few limitations.In the example you just deployed, it is a simple Golang server that responds to requests on port 8080 using the `MESSAGE` environment variable that will be set in the Worker and an [auto-generated environment variable](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/#environment-variables) `CLOUDFLARE_DEPLOYMENT_ID.````func handler(w http.ResponseWriter, r *http.Request) { message := os.Getenv("MESSAGE") instanceId := os.Getenv("CLOUDFLARE_DEPLOYMENT_ID") fmt.Fprintf(w, "Hi, I'm a container and this is my message: %s, and my instance ID is: %s", message, instanceId)}```NoteAfter deploying the example code, to deploy a different image, you can replace the provided image with one of your own.### Worker code#### Container ConfigurationFirst note `MyContainer` which extends the [Container ↗](https://github.com/cloudflare/containers) class:JavaScript```export class MyContainer extends Container { defaultPort = 8080; sleepAfter = '10s'; envVars = { MESSAGE: 'I was passed in via the container class!', }; override onStart() { console.log('Container successfully started'); } override onStop() { console.log('Container successfully shut down'); } override onError(error: unknown) { console.log('Container error:', error); }}```Explain CodeThis defines basic configuration for the container:* `defaultPort` sets the port that the `fetch` and `containerFetch` methods will use to communicate with the container. It also blocks requests until the container is listening on this port.* `sleepAfter` sets the timeout for the container to sleep after it has been idle for a certain amount of time.* `envVars` sets environment variables that will be passed to the container when it starts.* `onStart`, `onStop`, and `onError` are hooks that run when the container starts, stops, or errors, respectively.See the [Container class documentation](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/container-class/) for more details and configuration options.#### Routing to ContainersWhen a request enters Cloudflare, your Worker's [fetch handler](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/runtime-apis/handlers/fetch/) is invoked. This is the code that handles the incoming request. The fetch handler in the example code, launches containers in two ways, on different routes:* Making requests to `/container/` passes requests to a new container for each path. This is done by spinning up a new Container instance. You may note that the first request to a new path takes longer than subsequent requests, this is because a new container is booting.JavaScript```if (pathname.startsWith("/container")) { const container = env.MY_CONTAINER.getByName(pathname); return await container.fetch(request);}```* Making requests to `/lb` will load balance requests across several containers. This uses a simple `getRandom` helper method, which picks an ID at random from a set number (in this case 3), then routes to that Container instance. You can replace this with any routing or load balancing logic you choose to implement:JavaScript```if (pathname.startsWith("/lb")) { const container = await getRandom(env.MY_CONTAINER, 3); return await container.fetch(request);}```This allows for multiple ways of using Containers:* If you simply want to send requests to many stateless and interchangeable containers, you should load balance.* If you have stateful services or need individually addressable containers, you should request specific Container instances.* If you are running short-lived jobs, want fine-grained control over the container lifecycle, want to parameterize container entrypoint or env vars, or want to chain together multiple container calls, you should request specific Container instances.NoteCurrently, routing requests to one of many interchangeable Container instances is accomplished with the `getRandom` helper.This is temporary — we plan to add native support for latency-aware autoscaling and load balancing in the coming months.## View Containers in your DashboardThe [Containers Dashboard ↗](http://dash.cloudflare.com/?to=/:account/workers/containers) shows you helpful information about your Containers, including:* Status and Health* Metrics* Logs* A link to associated Workers and Durable ObjectsAfter launching your Worker, navigate to the Containers Dashboard by clicking on "Containers" under "Workers & Pages" in your sidebar.## Next StepsTo do more:* Modify the image by changing the Dockerfile and calling `wrangler deploy`* Review our [examples](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples) for more inspiration* Get [more information on the Containers Beta](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/beta-info)```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/get-started/","name":"Getting started"}}]}```------title: Container Interfacedescription: API reference for the Container interface and utility functionsimage: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/container-class.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Container InterfaceThe [Container class ↗](https://github.com/cloudflare/containers) from [@cloudflare/containers ↗](https://www.npmjs.com/package/@cloudflare/containers) is the standard way to interact with container instances from a Worker. It wraps the underlying [Durable Object interface](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/durable-object-methods) and provides a higher-level API for common container behaviors.NoteIf you are using containers to run sandboxed code execution — for example, inside an AI agent — consider the [Sandbox SDK](https://backiee.wasmer.app/https_developers_cloudflare_com/sandbox/) instead. It builds on top of Containers and provides a higher-level API for common sandbox workflows. npm yarn pnpm bun```npm i @cloudflare/containers``````yarn add @cloudflare/containers``````pnpm add @cloudflare/containers``````bun add @cloudflare/containers```Then, define a class that extends `Container` and set the shared properties on the class:* [ JavaScript ](#tab-panel-4081)* [ TypeScript ](#tab-panel-4082)JavaScript```import { Container, getContainer } from "@cloudflare/containers";export class SandboxContainer extends Container { defaultPort = 8080; requiredPorts = [8080, 9222]; sleepAfter = "5m"; envVars = { NODE_ENV: "production", LOG_LEVEL: "info", }; entrypoint = ["npm", "run", "start"]; enableInternet = false; pingEndpoint = "localhost/ready";}export default { async fetch(request, env) { return getContainer(env.SANDBOX_CONTAINER, "workspace-123").fetch(request); },};```Explain CodeTypeScript```import { Container, getContainer } from "@cloudflare/containers";export class SandboxContainer extends Container { defaultPort = 8080; requiredPorts = [8080, 9222]; sleepAfter = "5m"; envVars = { NODE_ENV: "production", LOG_LEVEL: "info", }; entrypoint = ["npm", "run", "start"]; enableInternet = false; pingEndpoint = "localhost/ready";}export default { async fetch(request: Request, env) { return getContainer(env.SANDBOX_CONTAINER, "workspace-123").fetch(request); },};```Explain CodeThe `Container` class extends `DurableObject`, so all [Durable Object](https://backiee.wasmer.app/https_developers_cloudflare_com/durable-objects) functionality is available.## PropertiesConfigure these as class fields on your subclass. They apply to every instance of the container.* **`defaultPort`** (`number`, optional) — the port your container process listens on. [fetch()](#fetch) and[containerFetch()](#containerfetch) forward requests here unless you specify a different port via [switchPort()](#switchport) or the `port` argument to[containerFetch()](#containerfetch). Most subclasses set this.* **`requiredPorts`** (`number[]`, optional) — ports that must be accepting connections before the container is considered ready. Used by [startAndWaitForPorts()](#startandwaitforports) when no`ports` argument is passed. Set this when your container runs multiple services that all need to be healthy before serving traffic.* **`sleepAfter`** (`string | number`, default:`"10m"`) — how long to keep the container alive without activity before shutting it down. Accepts a number of seconds or a duration string such as`"30s"`, `"5m"`, or `"1h"`. Activity resets the timer — see[renewActivityTimeout()](#renewactivitytimeout) for manual resets.* **`envVars`** (`Record<string, string>`, default: `{}`) — environment variables passed to the container on every start. For per-instance variables, pass `envVars` through [startAndWaitForPorts()](#startandwaitforports) instead.* **`entrypoint`** (`string[]`, optional) — overrides the image's default entrypoint. Useful when you want to run a different command without rebuilding the image, such as a dev server or a one-off task.* **`enableInternet`** (`boolean`, default:`true`) — controls whether the container can make outbound HTTP requests. Set to `false` for sandboxed environments where you want to intercept or block all outbound traffic. For more information, refer to [Handle outbound traffic](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/outbound-traffic/).* **`pingEndpoint`** (`string`, default:`"ping"`) — the host and path the class uses to health-check the container during startup. Most users do not need to change this.## Lifecycle hooksOverride these methods to run Worker code when the container changes state. Refer to the [status hooks example](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/status-hooks/) for a full example.### `onStart`Run Worker code after the container has started.TypeScript```onStart(): void | Promise<void>```**Returns**: `void | Promise<void>`. Resolve after any startup logic finishes.Use this to log startup, seed data, or schedule recurring tasks with [schedule()](#schedule).* [ JavaScript ](#tab-panel-4077)* [ TypeScript ](#tab-panel-4078)JavaScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; async onStart() { await this.containerFetch("http://localhost/bootstrap", { method: "POST", }); }}```Explain CodeTypeScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; override async onStart() { await this.containerFetch("http://localhost/bootstrap", { method: "POST", }); }}```Explain Code### `onStop`Run Worker code after the container process exits.TypeScript```onStop(params: StopParams): void | Promise<void>```**Parameters**:* `params.exitCode` \- Container process exit code.* `params.reason` \- Why the container stopped: `'exit'` when the process exited on its own, or `'runtime_signal'` when the runtime signalled it.**Returns**: `void | Promise<void>`. Resolve after your shutdown logic finishes.Use this to log, alert, or restart the container.* [ JavaScript ](#tab-panel-4075)* [ TypeScript ](#tab-panel-4076)JavaScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { onStop({ exitCode, reason }) { console.log("Container stopped", { exitCode, reason }); }}```TypeScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { override onStop({ exitCode, reason }) { console.log("Container stopped", { exitCode, reason }); }}```### `onError`Handle startup and port-checking errors.TypeScript```onError(error: unknown): any```**Parameters**:* `error` \- The error thrown during startup or port checks.**Returns**: `any`. The default implementation logs the error and re-throws it.Override this to suppress errors, notify an external service, or attempt a restart.* [ JavaScript ](#tab-panel-4079)* [ TypeScript ](#tab-panel-4080)JavaScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { onError(error) { console.error("Container failed to start", error); throw error; }}```TypeScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { override onError(error: unknown) { console.error("Container failed to start", error); throw error; }}```### `onActivityExpired`Run Worker code when the [sleepAfter](#sleepafter) timer expires.TypeScript```onActivityExpired(): Promise<void>```**Returns**: `Promise<void>`. Resolve after your idle-time logic finishes.Called when the [sleepAfter](#sleepafter) timeout expires with no incoming requests. The default implementation calls [stop()](#stop).WarningIf you override `onActivityExpired()`, call [await this.stop()](#stop) or [await this.destroy()](#destroy). Otherwise, the container does not go to sleep.If you override this method without stopping the container, the timer renews and the hook fires again on the next expiry.* [ JavaScript ](#tab-panel-4083)* [ TypeScript ](#tab-panel-4084)JavaScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { sleepAfter = "2m"; async onActivityExpired() { const state = await this.getState(); console.log("Container is idle, stopping it now", state.status); await this.stop(); }}```Explain CodeTypeScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { sleepAfter = "2m"; override async onActivityExpired() { const state = await this.getState(); console.log("Container is idle, stopping it now", state.status); await this.stop(); }}```Explain Code## Request methods### `fetch`Handle incoming HTTP or WebSocket requests.TypeScript```fetch(request: Request): Promise<Response>```**Parameters**:* `request` \- The incoming request to proxy to the container.**Returns**: `Promise<Response>` from the container or from your custom routing logic.By default, `fetch` forwards the request to the container process at [defaultPort](#defaultport). The container is started automatically if it is not already running.Override `fetch` when you need routing logic, authentication, or other middleware before forwarding to the container. Inside the override, call [this.containerFetch()](#containerfetch) rather than `this.fetch()` to avoid infinite recursion:* [ JavaScript ](#tab-panel-4085)* [ TypeScript ](#tab-panel-4086)JavaScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; async fetch(request) { const url = new URL(request.url); if (url.pathname === "/health") { return new Response("ok"); } return this.containerFetch(request); }}```Explain CodeTypeScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; override async fetch(request: Request): Promise<Response> { const url = new URL(request.url); if (url.pathname === "/health") { return new Response("ok"); } return this.containerFetch(request); }}```Explain Code`fetch` is the only method that supports WebSocket proxying. Refer to the [WebSocket example](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/websocket/) for a full example.### `containerFetch`Send an HTTP request directly to the container process. Generally, users should prefer to use [fetch](#fetch) unless it has been overrriden.TypeScript```containerFetch(request: Request, port?: number): Promise<Response>containerFetch(url: string | URL, init?: RequestInit, port?: number): Promise<Response>```**Parameters**:* `request` \- Existing `Request` object to forward.* `url` \- URL to request when you are constructing a new request.* `init` \- Standard `RequestInit` options for the URL-based overload.* `port` \- Optional target port. If omitted, the class uses [defaultPort](#defaultport).**Returns**: `Promise<Response>` from the container.This is what the default [fetch()](#fetch) implementation calls internally, and it is what you should call from within an overridden [fetch()](#fetch) method to avoid infinite recursion. It also accepts a standard fetch-style signature with a URL string and `RequestInit`, which is useful when you are constructing a new request rather than forwarding an existing one.Does not support WebSockets. Use [fetch()](#fetch) with [switchPort()](#switchport) for those.* [ JavaScript ](#tab-panel-4095)* [ TypeScript ](#tab-panel-4096)JavaScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; async fetch(request) { const url = new URL(request.url); if (url.pathname === "/metrics") { return this.containerFetch( "http://localhost/internal/metrics", { headers: { authorization: request.headers.get("authorization") ?? "", }, }, 9090, ); } return this.containerFetch(request); }}```Explain CodeTypeScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; override async fetch(request: Request): Promise<Response> { const url = new URL(request.url); if (url.pathname === "/metrics") { return this.containerFetch( "http://localhost/internal/metrics", { headers: { authorization: request.headers.get("authorization") ?? "", }, }, 9090, ); } return this.containerFetch(request); }}```Explain Code## Start and stopIn most cases you do not need to call these methods directly. [fetch()](#fetch) and [containerFetch()](#containerfetch) start the container automatically. Call these explicitly when you need to pre-warm a container, run a task on a schedule, or control the lifecycle from within a lifecycle hook.### `startAndWaitForPorts`Start the container and wait until the target ports are accepting connections.TypeScript```startAndWaitForPorts(args?: StartAndWaitForPortsOptions): Promise<void>startAndWaitForPorts( ports?: number | number[], cancellationOptions?: CancellationOptions, startOptions?: ContainerStartConfigOptions,): Promise<void>```**Parameters**:* `args.ports` \- Port or ports to wait for. Port resolution order is explicit `ports`, then [requiredPorts](#requiredports), then [defaultPort](#defaultport).* `args.startOptions` \- Per-instance startup overrides.* `args.startOptions.envVars` \- Per-instance environment variables.* `args.startOptions.entrypoint` \- Entrypoint override for this start only.* `args.startOptions.enableInternet` \- Whether outbound internet access is allowed for this start.* `args.cancellationOptions.abort` \- Abort signal to cancel startup.* `args.cancellationOptions.instanceGetTimeoutMS` \- Maximum time to get a container instance and issue the start command. Default: `8000`.* `args.cancellationOptions.portReadyTimeoutMS` \- Maximum time to wait for all ports to become ready. Default: `20000`.* `args.cancellationOptions.waitInterval` \- Polling interval in milliseconds. Default: `300`.**Returns**: `Promise<void>`. Resolves after the target ports are ready and [onStart()](#onstart) has run.This is the safest way to explicitly start a container when you need to be certain it is ready before sending traffic.This method also supports positional `ports`, `cancellationOptions`, and `startOptions` arguments, but the object form is easier to read.* [ JavaScript ](#tab-panel-4091)* [ TypeScript ](#tab-panel-4092)JavaScript```import { getContainer } from "@cloudflare/containers";export default { async scheduled(_event, env) { const container = getContainer(env.API_CONTAINER, "tenant-42"); await container.startAndWaitForPorts({ ports: [8080, 9222], startOptions: { envVars: { API_KEY: env.API_KEY, TENANT_ID: "tenant-42", }, }, cancellationOptions: { portReadyTimeoutMS: 30_000, }, }); },};```Explain CodeTypeScript```import { getContainer } from "@cloudflare/containers";export default { async scheduled(_event, env) { const container = getContainer(env.API_CONTAINER, "tenant-42"); await container.startAndWaitForPorts({ ports: [8080, 9222], startOptions: { envVars: { API_KEY: env.API_KEY, TENANT_ID: "tenant-42", }, }, cancellationOptions: { portReadyTimeoutMS: 30_000, }, }); },};```Explain CodeRefer to the [env vars and secrets example](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/env-vars-and-secrets/) for a full example.### `start`Start the container without waiting for all ports to become ready.TypeScript```start(startOptions?: ContainerStartConfigOptions, waitOptions?: WaitOptions): Promise<void>```**Parameters**:* `startOptions` \- Per-instance startup overrides.* `startOptions.envVars` \- Per-instance environment variables.* `startOptions.entrypoint` \- Entrypoint override for this start only.* `startOptions.enableInternet` \- Whether outbound internet access is allowed for this start.* `waitOptions.portToCheck` \- Port to probe while starting. If omitted, the class uses [defaultPort](#defaultport), the first [requiredPorts](#requiredports) entry, or a fallback port.* `waitOptions.signal` \- Abort signal to cancel startup.* `waitOptions.retries` \- Maximum number of start attempts before the method throws.* `waitOptions.waitInterval` \- Polling interval in milliseconds between retries.**Returns**: `Promise<void>`. Resolves after the start attempt succeeds and [onStart()](#onstart) has run.Use this when the container does not expose ports, such as a batch job or a cron task, or when you want to manage readiness yourself with [waitForPort()](#waitforport). If you need to wait for all ports to be ready, use [startAndWaitForPorts()](#startandwaitforports) instead.* [ JavaScript ](#tab-panel-4087)* [ TypeScript ](#tab-panel-4088)JavaScript```import { getContainer } from "@cloudflare/containers";export default { async scheduled(_event, env) { const container = getContainer(env.JOB_CONTAINER, "nightly-report"); await container.start({ entrypoint: ["node", "scripts/nightly-report.js"], envVars: { REPORT_DATE: new Date().toISOString(), }, enableInternet: false, }); },};```Explain CodeTypeScript```import { getContainer } from "@cloudflare/containers";export default { async scheduled(_event, env) { const container = getContainer(env.JOB_CONTAINER, "nightly-report"); await container.start({ entrypoint: ["node", "scripts/nightly-report.js"], envVars: { REPORT_DATE: new Date().toISOString(), }, enableInternet: false, }); },};```Explain CodeRefer to the [cron example](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/cron/) for a full example.### `waitForPort`Poll a single port until it accepts connections.TypeScript```waitForPort(waitOptions: WaitOptions): Promise<number>```**Parameters**:* `waitOptions.portToCheck` \- Port number to check.* `waitOptions.signal` \- Abort signal to cancel waiting.* `waitOptions.retries` \- Maximum number of retries before the method throws.* `waitOptions.waitInterval` \- Polling interval in milliseconds.**Returns**: `Promise<number>`. The numeric return value is mainly useful when you are coordinating custom readiness logic across multiple waits.Throws if the port does not become available within the retry limit. Use this after [start()](#start) when you need to check multiple ports independently or in a specific sequence.* [ JavaScript ](#tab-panel-4089)* [ TypeScript ](#tab-panel-4090)JavaScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { async warmInspector() { await this.start(); const retryCount = await this.waitForPort({ portToCheck: 9222, retries: 20, waitInterval: 500, }); console.log("Inspector port became ready:", retryCount); }}```Explain CodeTypeScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { async warmInspector() { await this.start(); const retryCount = await this.waitForPort({ portToCheck: 9222, retries: 20, waitInterval: 500, }); console.log("Inspector port became ready:", retryCount); }}```Explain Code### `stop`Send a signal to the container process.TypeScript```stop(signal?: 'SIGTERM' | 'SIGINT' | 'SIGKILL' | number): Promise<void>```**Parameters**:* `signal` \- Signal to send. Defaults to `'SIGTERM'`.**Returns**: `Promise<void>`. Resolves after the signal is sent and pending stop handling has completed.Defaults to `SIGTERM`, which gives the process a chance to shut down gracefully. Triggers [onStop()](#onstop).* [ JavaScript ](#tab-panel-4093)* [ TypeScript ](#tab-panel-4094)JavaScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; async fetch(request) { if (new URL(request.url).pathname === "/admin/stop") { await this.stop(); return new Response("Container is stopping"); } return this.containerFetch(request); }}```Explain CodeTypeScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; override async fetch(request: Request): Promise<Response> { if (new URL(request.url).pathname === "/admin/stop") { await this.stop(); return new Response("Container is stopping"); } return this.containerFetch(request); }}```Explain Code### `destroy`Immediately kill the container process.TypeScript```destroy(): Promise<void>```**Returns**: `Promise<void>`. Resolves after the runtime has destroyed the container.This sends `SIGKILL`. Use it when you need the container gone immediately and cannot wait for a graceful shutdown. Triggers [onStop()](#onstop).* [ JavaScript ](#tab-panel-4097)* [ TypeScript ](#tab-panel-4098)JavaScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; async fetch(request) { if (new URL(request.url).pathname === "/admin/destroy") { await this.destroy(); return new Response("Container destroyed"); } return this.containerFetch(request); }}```Explain CodeTypeScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; override async fetch(request: Request): Promise<Response> { if (new URL(request.url).pathname === "/admin/destroy") { await this.destroy(); return new Response("Container destroyed"); } return this.containerFetch(request); }}```Explain Code## State and monitoring### `getState`Read the current container state.TypeScript```getState(): Promise<State>```**Returns**: `Promise<State>` with:* `status` \- One of `'running'`, `'healthy'`, `'stopping'`, `'stopped'`, or `'stopped_with_code'`.* `lastChange` \- Unix timestamp in milliseconds for the last state change.* `exitCode` \- Optional exit code when `status` is `'stopped_with_code'`.`running` means the container is starting and has not yet passed its health check. `healthy` means it is up and accepting requests.* [ JavaScript ](#tab-panel-4099)* [ TypeScript ](#tab-panel-4100)JavaScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { async logState() { const state = await this.getState(); if (state.status === "stopped_with_code") { console.error("Container exited with code", state.exitCode); return; } console.log("Container status:", state.status); }}```Explain CodeTypeScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { async logState() { const state = await this.getState(); if (state.status === "stopped_with_code") { console.error("Container exited with code", state.exitCode); return; } console.log("Container status:", state.status); }}```Explain Code### `renewActivityTimeout`Reset the [sleepAfter](#sleepafter) timer.TypeScript```renewActivityTimeout(): void```**Returns**: `void`.Incoming requests reset the timer automatically. Call this manually from background work, such as a scheduled task or a long-running operation, that should count as activity and prevent the container from sleeping.* [ JavaScript ](#tab-panel-4103)* [ TypeScript ](#tab-panel-4104)JavaScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; async processJobs(jobIds) { for (const jobId of jobIds) { this.renewActivityTimeout(); await this.containerFetch(`http://localhost/jobs/${jobId}`, { method: "POST", }); } }}```Explain CodeTypeScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; async processJobs(jobIds: string[]) { for (const jobId of jobIds) { this.renewActivityTimeout(); await this.containerFetch(`http://localhost/jobs/${jobId}`, { method: "POST", }); } }}```Explain Code## Scheduling### `schedule`Schedule a method on the class to run later.TypeScript```schedule<T>(when: Date | number, callback: string, payload?: T): Promise<Schedule<T>>```**Parameters**:* `when` \- Either a `Date` for a specific time or a number of seconds to delay.* `callback` \- Name of the class method to call.* `payload` \- Optional data passed to the callback method.**Returns**: `Promise<Schedule<T>>` with:* `taskId` \- Unique schedule ID.* `callback` \- Method name that will be called.* `payload` \- Payload that will be passed to the callback.* `type` \- `'scheduled'` for an absolute time or `'delayed'` for a relative delay.* `time` \- Unix timestamp in seconds when the task will run.* `delayInSeconds` \- Delay in seconds when `type` is `'delayed'`.Do not override [alarm() ↗](https://backiee.wasmer.app/https_developers_cloudflare_com/durable-objects/api/alarms/) directly. The `Container` class uses the alarm handler to manage the container lifecycle, so use [schedule()](#schedule) instead.The following example schedules a recurring health report starting at container startup:* [ JavaScript ](#tab-panel-4107)* [ TypeScript ](#tab-panel-4108)JavaScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; async onStart() { await this.schedule(60, "healthReport"); } async healthReport() { const state = await this.getState(); console.log("Container status:", state.status); await this.schedule(60, "healthReport"); }}```Explain CodeTypeScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; override async onStart() { await this.schedule(60, "healthReport"); } async healthReport() { const state = await this.getState(); console.log("Container status:", state.status); await this.schedule(60, "healthReport"); }}```Explain Code## Outbound interceptionOutbound interception lets you intercept, mock, or block HTTP requests that the container makes to external hosts. This is useful for sandboxing, testing, or proxying outbound traffic through Worker code.* [ JavaScript ](#tab-panel-4111)* [ TypeScript ](#tab-panel-4112)JavaScript```import { Container, ContainerProxy, getContainer,} from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; enableInternet = true; static outboundByHost = { "blocked.example.com": () => { return new Response("Blocked", { status: 403 }); }, }; static outbound = async (request, _env, ctx) => { console.log(`[${ctx.containerId}] outbound:`, request.url); return fetch(request); };}export { ContainerProxy };export default { async fetch(request, env) { return getContainer(env.MY_CONTAINER).fetch(request); },};```Explain CodeTypeScript```import { Container, ContainerProxy, getContainer,} from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; enableInternet = true; static outboundByHost = { "blocked.example.com": () => { return new Response("Blocked", { status: 403 }); }, }; static outbound = async (request, _env, ctx) => { console.log(`[${ctx.containerId}] outbound:`, request.url); return fetch(request); };}export { ContainerProxy };export default { async fetch(request: Request, env) { return getContainer(env.MY_CONTAINER).fetch(request); },};```Explain CodeFor more information, refer to [Handle outbound traffic](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/outbound-traffic/).## Utility functionsThese functions are exported alongside the `Container` class from `@cloudflare/containers`.### `getContainer`Get a stub for a named container instance.TypeScript```getContainer<T>(binding: DurableObjectNamespace<T>, name?: string): DurableObjectStub<T>```**Parameters**:* `binding` \- Durable Object namespace binding for your container class.* `name` \- Stable instance name. Defaults to `cf-singleton-container`.**Returns**: `DurableObjectStub<T>` for the named container instance.Use this when you want one container per logical entity, such as a user session, a document, or a game room, identified by a stable name.* [ JavaScript ](#tab-panel-4101)* [ TypeScript ](#tab-panel-4102)JavaScript```import { getContainer } from "@cloudflare/containers";export default { async fetch(request, env) { const { sessionId } = await request.json(); return getContainer(env.MY_CONTAINER, sessionId).fetch(request); },};```TypeScript```import { getContainer } from "@cloudflare/containers";export default { async fetch(request: Request, env) { const { sessionId } = await request.json(); return getContainer(env.MY_CONTAINER, sessionId).fetch(request); },};```### `getRandom`Get a stub for a randomly selected container instance.TypeScript```getRandom<T>(binding: DurableObjectNamespace<T>, instances?: number): Promise<DurableObjectStub<T>>```**Parameters**:* `binding` \- Durable Object namespace binding for your container class.* `instances` \- Total number of instances to choose from. Defaults to `3`.**Returns**: `Promise<DurableObjectStub<T>>` for the randomly selected instance.Use this for stateless workloads where any container can handle any request and you want to spread load across multiple instances.* [ JavaScript ](#tab-panel-4105)* [ TypeScript ](#tab-panel-4106)JavaScript```import { getRandom } from "@cloudflare/containers";export default { async fetch(request, env) { const container = await getRandom(env.WORKER_POOL, 5); return container.fetch(request); },};```TypeScript```import { getRandom } from "@cloudflare/containers";export default { async fetch(request: Request, env) { const container = await getRandom(env.WORKER_POOL, 5); return container.fetch(request); },};```Refer to the [stateless instances example](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/stateless/) for a full example.### `switchPort`Target a different container port while still using `fetch()`.TypeScript```switchPort(request: Request, port: number): Request```**Parameters**:* `request` \- Request to copy.* `port` \- Port to encode into the request headers.**Returns**: `Request` copy with the target port set.Use this when you need to target a specific port and also need WebSocket support. If you do not need WebSockets, pass the port directly to [containerFetch()](#containerfetch) instead.* [ JavaScript ](#tab-panel-4109)* [ TypeScript ](#tab-panel-4110)JavaScript```import { getContainer, switchPort } from "@cloudflare/containers";export default { async fetch(request, env) { const container = getContainer(env.MY_CONTAINER); return container.fetch(switchPort(request, 9090)); },};```TypeScript```import { getContainer, switchPort } from "@cloudflare/containers";export default { async fetch(request: Request, env) { const container = getContainer(env.MY_CONTAINER); return container.fetch(switchPort(request, 9090)); },};``````json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/container-class/","name":"Container Interface"}}]}```------title: Examplesdescription: Explore the following examples of Container functionality:image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/examples/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# ExamplesExplore the following examples of Container functionality:[Mount R2 buckets with FUSEMount R2 buckets as filesystems using FUSE in Containers](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/r2-fuse-mount/)[Static Frontend, Container BackendA simple frontend app with a containerized backend](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/container-backend/)[Cron ContainerRunning a container on a schedule using Cron Triggers](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/cron/)[Using Durable Objects DirectlyVarious examples calling Containers directly from Durable Objects](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/durable-object-interface/)[Env Vars and SecretsPass in environment variables and secrets to your container](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/env-vars-and-secrets/)[Stateless InstancesRun multiple instances across Cloudflare's network](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/stateless/)[Status HooksExecute Workers code in reaction to Container status changes](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/status-hooks/)[Websocket to ContainerForwarding a Websocket request to a Container](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/websocket/)```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/examples/","name":"Examples"}}]}```------title: Static Frontend, Container Backenddescription: A simple frontend app with a containerized backendimage: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/examples/container-backend.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Static Frontend, Container Backend**Last reviewed:** 10 months agoA simple frontend app with a containerized backendA common pattern is to serve a static frontend application (e.g., React, Vue, Svelte) using Static Assets, then pass backend requests to a containerized backend application.In this example, we'll show an example using a simple `index_html` file served as a static asset, but you can select from one of many frontend frameworks. See our [Workers framework examples](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/framework-guides/web-apps/) for more information.For a full example, see the [Static Frontend + Container Backend Template ↗](https://github.com/mikenomitch/static-frontend-container-backend).## Configure Static Assets and a Container* [ wrangler.jsonc ](#tab-panel-4113)* [ wrangler.toml ](#tab-panel-4114)JSONC```{ "name": "cron-container", "main": "src/index.ts", "assets": { "directory": "./dist", "binding": "ASSETS" }, "containers": [ { "class_name": "Backend", "image": "./Dockerfile", "max_instances": 3 } ], "durable_objects": { "bindings": [ { "class_name": "Backend", "name": "BACKEND" } ] }, "migrations": [ { "new_sqlite_classes": [ "Backend" ], "tag": "v1" } ]}```Explain CodeTOML```name = "cron-container"main = "src/index.ts"[assets]directory = "./dist"binding = "ASSETS"[[containers]]class_name = "Backend"image = "./Dockerfile"max_instances = 3[[durable_objects.bindings]]class_name = "Backend"name = "BACKEND"[[migrations]]new_sqlite_classes = [ "Backend" ]tag = "v1"```Explain Code## Add a simple index_html file to serveCreate a simple `index_html` file in the `./dist` directory.index_html```<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Widgets</title> <script defer src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.13.3/cdn.min.js"></script></head><body> <div x-data="widgets()" x-init="fetchWidgets()"> <h1>Widgets</h1> <div x-show="loading">Loading...</div> <div x-show="error" x-text="error" style="color: red;"></div> <ul x-show="!loading && !error"> <template x-for="widget in widgets" :key="widget.id"> <li> <span x-text="widget.name"></span> - (ID: <span x-text="widget.id"></span>) </li> </template> </ul> <div x-show="!loading && !error && widgets.length === 0"> No widgets found. </div> </div> <script> function widgets() { return { widgets: [], loading: false, error: null, async fetchWidgets() { this.loading = true; this.error = null; try { const response = await fetch('/api/widgets'); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } this.widgets = await response.json(); } catch (err) { this.error = err.message; } finally { this.loading = false; } } } } </script></body></html>```Explain CodeIn this example, we are using [Alpine.js ↗](https://alpinejs.dev/) to fetch a list of widgets from `/api/widgets`.This is meant to be a very simple example, but you can get significantly more complex. See [examples of Workers integrating with frontend frameworks](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/framework-guides/web-apps/) for more information.## Define a WorkerYour Worker needs to be able to both serve static assets and route requests to the containerized backend.In this case, we will pass requests to one of three container instances if the route starts with `/api`, and all other requests will be served as static assets.JavaScript```import { Container, getRandom } from "@cloudflare/containers";const INSTANCE_COUNT = 3;class Backend extends Container { defaultPort = 8080; // pass requests to port 8080 in the container sleepAfter = "2h"; // only sleep a container if it hasn't gotten requests in 2 hours}export default { async fetch(request, env) { const url = new URL(request.url); if (url.pathname.startsWith("/api")) { // note: "getRandom" to be replaced with latency-aware routing in the near future const containerInstance = await getRandom(env.BACKEND, INSTANCE_COUNT); return containerInstance.fetch(request); } return env.ASSETS.fetch(request); },};```Explain CodeNoteThis example uses the `getRandom` function, which is a temporary helper that will randomly select of of N instances of a Container to route requests to.In the future, we will provide improved latency-aware load balancing and autoscaling.This will make scaling stateless instances simple and routing more efficient. See the[autoscaling documentation](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/scaling-and-routing) for more details.## Define a backend containerYour container should be able to handle requests to `/api/widgets`.In this case, we'll use a simple Golang backend that returns a hard-coded list of widgets.server.go```package mainimport ( "encoding/json" "log" "net/http")func handler(w http.ResponseWriter, r \*http.Request) { widgets := []map[string]interface{}{ {"id": 1, "name": "Widget A"}, {"id": 2, "name": "Sprocket B"}, {"id": 3, "name": "Gear C"}, } w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") json.NewEncoder(w).Encode(widgets)}func main() { http.HandleFunc("/api/widgets", handler) log.Fatal(http.ListenAndServe(":8080", nil))}```Explain Code```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/examples/container-backend/","name":"Static Frontend, Container Backend"}}]}```------title: Cron Containerdescription: Running a container on a schedule using Cron Triggersimage: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/examples/cron.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Cron Container**Last reviewed:** 10 months agoRunning a container on a schedule using Cron TriggersTo launch a container on a schedule, you can use a Workers [Cron Trigger](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/configuration/cron-triggers/).For a full example, see the [Cron Container Template ↗](https://github.com/mikenomitch/cron-container/tree/main).Use a cron expression in your Wrangler config to specify the schedule:* [ wrangler.jsonc ](#tab-panel-4115)* [ wrangler.toml ](#tab-panel-4116)JSONC```{ "name": "cron-container", "main": "src/index.ts", "triggers": { "crons": [ "*/2 * * * *" // Run every 2 minutes ] }, "containers": [ { "class_name": "CronContainer", "image": "./Dockerfile" } ], "durable_objects": { "bindings": [ { "class_name": "CronContainer", "name": "CRON_CONTAINER" } ] }, "migrations": [ { "new_sqlite_classes": ["CronContainer"], "tag": "v1" } ]}```Explain CodeTOML```name = "cron-container"main = "src/index.ts"[triggers]crons = [ "*/2 * * * *" ][[containers]]class_name = "CronContainer"image = "./Dockerfile"[[durable_objects.bindings]]class_name = "CronContainer"name = "CRON_CONTAINER"[[migrations]]new_sqlite_classes = [ "CronContainer" ]tag = "v1"```Explain CodeThen in your Worker, call your Container from the "scheduled" handler:TypeScript```import { Container, getContainer } from '@cloudflare/containers';export class CronContainer extends Container { sleepAfter = '10s'; override onStart() { console.log('Starting container'); } override onStop() { console.log('Container stopped'); }}export default { async fetch(): Promise<Response> { return new Response("This Worker runs a cron job to execute a container on a schedule."); }, async scheduled(_controller: any, env: { CRON_CONTAINER: DurableObjectNamespace<CronContainer> }) { let container = getContainer(env.CRON_CONTAINER); await container.start({ envVars: { MESSAGE: "Start Time: " + new Date().toISOString(), } }) },};```Explain Code```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/examples/cron/","name":"Cron Container"}}]}```------title: Using Durable Objects Directlydescription: Various examples calling Containers directly from Durable Objectsimage: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/examples/durable-object-interface.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Using Durable Objects Directly**Last reviewed:** 10 months agoVarious examples calling Containers directly from Durable Objects```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/examples/durable-object-interface/","name":"Using Durable Objects Directly"}}]}```------title: Env Vars and Secretsdescription: Pass in environment variables and secrets to your containerimage: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/examples/env-vars-and-secrets.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Env Vars and Secrets**Last reviewed:** 10 months agoPass in environment variables and secrets to your containerEnvironment variables can be passed into a Container using the `envVars` field in the [Container](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/container-class/) class, or by setting manually when the Container starts.Secrets can be passed into a Container by using [Worker Secrets](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/configuration/secrets/)or the [Secret Store](https://backiee.wasmer.app/https_developers_cloudflare_com/secrets-store/integrations/workers/), then passing them into the Container as environment variables.KV values can be passed into a Container by using [Workers KV](https://backiee.wasmer.app/https_developers_cloudflare_com/kv/), then reading the values and passing them into the Container as environment variables.These examples show the various ways to pass in secrets, KV values, and environment variables. In each, we will be passing in:* the variable `"ENV_VAR"` as a hard-coded environment variable* the secret `"WORKER_SECRET"` as a secret from Worker Secrets* the secret `"SECRET_STORE_SECRET"` as a secret from the Secret Store* the value `"KV_VALUE"` as a value from Workers KVIn practice, you may just use one of the methods for storing secrets and data, but we will show all methods for completeness.## Creating secrets and KV dataFirst, let's create the `"WORKER_SECRET"` secret in Worker Secrets: npm yarn pnpm```npx wrangler secret put WORKER_SECRET``````yarn wrangler secret put WORKER_SECRET``````pnpm wrangler secret put WORKER_SECRET```Then, let's create a store called "demo" in the Secret Store, and add the `"SECRET_STORE_SECRET"` secret to it: npm yarn pnpm```npx wrangler secrets-store store create demo --remote``````yarn wrangler secrets-store store create demo --remote``````pnpm wrangler secrets-store store create demo --remote``` npm yarn pnpm```npx wrangler secrets-store secret create demo --name SECRET_STORE_SECRET --scopes workers --remote``````yarn wrangler secrets-store secret create demo --name SECRET_STORE_SECRET --scopes workers --remote``````pnpm wrangler secrets-store secret create demo --name SECRET_STORE_SECRET --scopes workers --remote```Next, let's create a KV namespace called `DEMO_KV` and add a key-value pair: npm yarn pnpm```npx wrangler kv namespace create DEMO_KV``````yarn wrangler kv namespace create DEMO_KV``````pnpm wrangler kv namespace create DEMO_KV``` npm yarn pnpm```npx wrangler kv key put --binding DEMO_KV KV_VALUE 'Hello from KV!'``````yarn wrangler kv key put --binding DEMO_KV KV_VALUE 'Hello from KV!'``````pnpm wrangler kv key put --binding DEMO_KV KV_VALUE 'Hello from KV!'```For full details on how to create secrets, see the [Workers Secrets documentation](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/configuration/secrets/)and the [Secret Store documentation](https://backiee.wasmer.app/https_developers_cloudflare_com/secrets-store/integrations/workers/). For KV setup, see the [Workers KV documentation](https://backiee.wasmer.app/https_developers_cloudflare_com/kv/).## Adding bindingsNext, we need to add bindings to access our secrets, KV values, and environment variables in Wrangler configuration.* [ wrangler.jsonc ](#tab-panel-4117)* [ wrangler.toml ](#tab-panel-4118)JSONC```{ "name": "my-container-worker", "vars": { "ENV_VAR": "my-env-var" }, "secrets_store_secrets": [ { "binding": "SECRET_STORE", "store_id": "demo", "secret_name": "SECRET_STORE_SECRET" } ], "kv_namespaces": [ { "binding": "DEMO_KV", "id": "<your-kv-namespace-id>" } ] // rest of the configuration...}```Explain CodeTOML```name = "my-container-worker"[vars]ENV_VAR = "my-env-var"[[secrets_store_secrets]]binding = "SECRET_STORE"store_id = "demo"secret_name = "SECRET_STORE_SECRET"[[kv_namespaces]]binding = "DEMO_KV"id = "<your-kv-namespace-id>"```Explain CodeNote that `"WORKER_SECRET"` does not need to be specified in the Wrangler config file, as it is automatically added to `env`.Also note that we did not configure anything specific for environment variables, secrets, or KV values in the _container-related_ portion of the Wrangler configuration file.## Using `envVars` on the Container classNow, let's pass the env vars and secrets to our container using the `envVars` field in the `Container` class:JavaScript```// https://backiee.wasmer.app/https_developers_cloudflare_com/workers/runtime-apis/bindings/#importing-env-as-a-globalimport { env } from "cloudflare:workers";export class MyContainer extends Container { defaultPort = 8080; sleepAfter = "10s"; envVars = { WORKER_SECRET: env.WORKER_SECRET, ENV_VAR: env.ENV_VAR, // we can't set the secret store binding or KV values as defaults here, as getting their values is asynchronous };}```Explain CodeEvery instance of this `Container` will now have these variables and secrets set as environment variables when it launches.## Setting environment variables per-instanceBut what if you want to set environment variables on a per-instance basis?In this case, use the `startAndWaitForPorts()` method to pass in environment variables for each instance.JavaScript```export class MyContainer extends Container { defaultPort = 8080; sleepAfter = "10s";}export default { async fetch(request, env) { if (new URL(request.url).pathname === "/launch-instances") { let instanceOne = env.MY_CONTAINER.getByName("foo"); let instanceTwo = env.MY_CONTAINER.getByName("bar"); // Each instance gets a different set of environment variables await instanceOne.startAndWaitForPorts({ startOptions: { envVars: { ENV_VAR: env.ENV_VAR + "foo", WORKER_SECRET: env.WORKER_SECRET, SECRET_STORE_SECRET: await env.SECRET_STORE.get(), KV_VALUE: await env.DEMO_KV.get("KV_VALUE"), }, }, }); await instanceTwo.startAndWaitForPorts({ startOptions: { envVars: { ENV_VAR: env.ENV_VAR + "bar", WORKER_SECRET: env.WORKER_SECRET, SECRET_STORE_SECRET: await env.SECRET_STORE.get(), KV_VALUE: await env.DEMO_KV.get("KV_VALUE"), // You can also read different KV keys for different instances INSTANCE_CONFIG: await env.DEMO_KV.get("instance-bar-config"), }, }, }); return new Response("Container instances launched"); } // ... etc ... },};```Explain Code## Reading KV values in containersKV values are particularly useful for configuration data that changes infrequently but needs to be accessible to your containers. Since KV operations are asynchronous, you must read the values at runtime when starting containers.Here are common patterns for using KV with containers:### Configuration dataJavaScript```export default { async fetch(request, env) { if (new URL(request.url).pathname === "/configure-container") { // Read configuration from KV const config = await env.DEMO_KV.get("container-config", "json"); const apiUrl = await env.DEMO_KV.get("api-endpoint"); let container = env.MY_CONTAINER.getByName("configured"); await container.startAndWaitForPorts({ startOptions: { envVars: { CONFIG_JSON: JSON.stringify(config), API_ENDPOINT: apiUrl, DEPLOYMENT_ENV: await env.DEMO_KV.get("deployment-env"), }, }, }); return new Response("Container configured and launched"); } },};```Explain Code### Feature flagsJavaScript```export default { async fetch(request, env) { if (new URL(request.url).pathname === "/launch-with-features") { // Read feature flags from KV const featureFlags = { ENABLE_FEATURE_A: await env.DEMO_KV.get("feature-a-enabled"), ENABLE_FEATURE_B: await env.DEMO_KV.get("feature-b-enabled"), DEBUG_MODE: await env.DEMO_KV.get("debug-enabled"), }; let container = env.MY_CONTAINER.getByName("features"); await container.startAndWaitForPorts({ startOptions: { envVars: { ...featureFlags, CONTAINER_VERSION: "1.2.3", }, }, }); return new Response("Container launched with feature flags"); } },};```Explain Code## Build-time environment variablesFinally, you can also set build-time environment variables that are only available when building the container image via the `image_vars` field in the Wrangler configuration.```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/examples/env-vars-and-secrets/","name":"Env Vars and Secrets"}}]}```------title: Mount R2 buckets with FUSEdescription: Mount R2 buckets as filesystems using FUSE in Containersimage: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/examples/r2-fuse-mount.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Mount R2 buckets with FUSE**Last reviewed:** 5 months agoMount R2 buckets as filesystems using FUSE in ContainersFUSE (Filesystem in Userspace) allows you to mount [R2 buckets](https://backiee.wasmer.app/https_developers_cloudflare_com/r2/) as filesystems within Containers. Applications can then interact with R2 using standard filesystem operations rather than object storage APIs.Common use cases include:* **Bootstrapping containers with assets** \- Mount datasets, models, or dependencies for sandboxes and agent environments* **Persisting user state** \- Store and access user configuration or application state without managing downloads* **Large static files** \- Avoid bloating container images or downloading files at startup* **Editing files** \- Make code or config available within the container and save edits across instances.Performance considerationsObject storage is not a POSIX-compatible filesystem, nor is it local storage. While FUSE mounts provide a familiar interface, you should not expect native SSD-like performance.Common use cases where this tradeoff is acceptable include reading shared assets, bootstrapping [agents](https://backiee.wasmer.app/https_developers_cloudflare_com/agents/) or [sandboxes](https://backiee.wasmer.app/https_developers_cloudflare_com/sandbox/) with initial data, persisting user state, and applications that require filesystem APIs but don't need high-performance I/O.## Mounting bucketsTo mount an R2 bucket, install a FUSE adapter in your Dockerfile and configure it to run at container startup.This example uses [tigrisfs ↗](https://github.com/tigrisdata/tigrisfs), which supports S3-compatible storage including R2:Dockerfile```FROM alpine:3.20# Install FUSE and dependenciesRUN apk add --no-cache \ --repository http://dl-cdn.alpinelinux.org/alpine/v3.20/main \ ca-certificates fuse curl bash# Install tigrisfsRUN ARCH=$(uname -m) && \ if [ "$ARCH" = "x86_64" ]; then ARCH="amd64"; fi && \ if [ "$ARCH" = "aarch64" ]; then ARCH="arm64"; fi && \ VERSION=$(curl -s https://api.github.com/repos/tigrisdata/tigrisfs/releases/latest | grep -o '"tag_name": "[^"]*' | cut -d'"' -f4) && \ curl -L "https://github.com/tigrisdata/tigrisfs/releases/download/${VERSION}/tigrisfs_${VERSION#v}_linux_${ARCH}.tar_gz" -o /tmp/tigrisfs.tar_gz && \ tar -xzf /tmp/tigrisfs.tar_gz -C /usr/local/bin/ && \ rm /tmp/tigrisfs.tar_gz && \ chmod +x /usr/local/bin/tigrisfs# Create startup script that mounts bucket and runs a commandRUN printf '#!/bin/sh\n\ set -e\n\ \n\ mkdir -p /mnt/r2\n\ \n\ R2_ENDPOINT="https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com"\n\ echo "Mounting bucket ${R2_BUCKET_NAME}..."\n\ /usr/local/bin/tigrisfs --endpoint "${R2_ENDPOINT}" -f "${R2_BUCKET_NAME}" /mnt/r2 &\n\ sleep 3\n\ \n\ echo "Contents of mounted bucket:"\n\ ls -lah /mnt/r2\n\ ' > /startup.sh && chmod +x /startup.shEXPOSE 8080CMD ["/startup.sh"]```Explain CodeThe startup script creates a mount point, starts tigrisfs in the background to mount the bucket, and then lists the mounted directory contents.### Passing credentials to the containerYour Container needs [R2 credentials](https://backiee.wasmer.app/https_developers_cloudflare_com/r2/api/tokens/) and configuration passed as environment variables. Store credentials as [Worker secrets](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/configuration/secrets/), then pass them through the `envVars` property:* [ JavaScript ](#tab-panel-4119)* [ TypeScript ](#tab-panel-4120)src/index.js```import { Container, getContainer } from "@cloudflare/containers";export class FUSEDemo extends Container { defaultPort = 8080; sleepAfter = "10m"; envVars = { AWS_ACCESS_KEY_ID: this.env.AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY: this.env.AWS_SECRET_ACCESS_KEY, R2_BUCKET_NAME: this.env.R2_BUCKET_NAME, R2_ACCOUNT_ID: this.env.R2_ACCOUNT_ID, };}```Explain Codesrc/index.ts```import { Container, getContainer } from "@cloudflare/containers";interface Env { FUSEDemo: DurableObjectNamespace<FUSEDemo>; AWS_ACCESS_KEY_ID: string; AWS_SECRET_ACCESS_KEY: string; R2_BUCKET_NAME: string; R2_ACCOUNT_ID: string;}export class FUSEDemo extends Container<Env> { defaultPort = 8080; sleepAfter = "10m"; envVars = { AWS_ACCESS_KEY_ID: this.env.AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY: this.env.AWS_SECRET_ACCESS_KEY, R2_BUCKET_NAME: this.env.R2_BUCKET_NAME, R2_ACCOUNT_ID: this.env.R2_ACCOUNT_ID, };}```Explain CodeThe `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` should be stored as secrets, while `R2_BUCKET_NAME` and `R2_ACCOUNT_ID` can be configured as variables in your `wrangler.jsonc`:Creating your R2 AWS API keysTo get your `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`, [head to your R2 dashboard ↗](https://dash.cloudflare.com/?to=/:account/r2/overview) and create a new R2 Access API key. Use the generated the `Access Key ID` as your `AWS_ACCESS_KEY_ID` and `Secret Access Key` is the `AWS_SECRET_ACCESS_KEY`.```{ "vars": { "R2_BUCKET_NAME": "my-bucket", "R2_ACCOUNT_ID": "your-account-id" }}```### Other S3-compatible storage providersOther S3-compatible storage providers, including AWS S3 and Google Cloud Storage, can be mounted using the same approach as R2\. You will need to provide the appropriate endpoint URL and access credentials for the storage provider.## Mounting bucket prefixesTo mount a specific prefix (subdirectory) within a bucket, most FUSE adapters require mounting the entire bucket and then accessing the prefix path within the mount.With tigrisfs, mount the bucket and access the prefix via the filesystem path:```RUN printf '#!/bin/sh\n\ set -e\n\ \n\ mkdir -p /mnt/r2\n\ \n\ R2_ENDPOINT="https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com"\n\ /usr/local/bin/tigrisfs --endpoint "${R2_ENDPOINT}" -f "${R2_BUCKET_NAME}" /mnt/r2 &\n\ sleep 3\n\ \n\ echo "Accessing prefix: ${BUCKET_PREFIX}"\n\ ls -lah "/mnt/r2/${BUCKET_PREFIX}"\n\ ' > /startup.sh && chmod +x /startup.sh```Explain CodeYour application can then read from `/mnt/r2/${BUCKET_PREFIX}` to access only the files under that prefix. Pass `BUCKET_PREFIX` as an environment variable alongside your other R2 configuration.## Mounting buckets as read-onlyTo prevent applications from writing to the mounted bucket, add the `-o ro` flag to mount the filesystem as read-only:```RUN printf '#!/bin/sh\n\ set -e\n\ \n\ mkdir -p /mnt/r2\n\ \n\ R2_ENDPOINT="https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com"\n\ /usr/local/bin/tigrisfs --endpoint "${R2_ENDPOINT}" -o ro -f "${R2_BUCKET_NAME}" /mnt/r2 &\n\ sleep 3\n\ \n\ ls -lah /mnt/r2\n\ ' > /startup.sh && chmod +x /startup.sh```Explain CodeThis is useful for shared assets or configuration files where you want to ensure applications only read data.## Related resources* [Container environment variables](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/env-vars-and-secrets/) \- Learn how to pass secrets and variables to Containers* [tigrisfs ↗](https://github.com/tigrisdata/tigrisfs) \- FUSE adapter for S3-compatible storage including R2* [s3fs ↗](https://github.com/s3fs-fuse/s3fs-fuse) \- Alternative FUSE adapter for S3-compatible storage* [gcsfuse ↗](https://github.com/GoogleCloudPlatform/gcsfuse) \- FUSE adapter for Google Cloud Storage buckets```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/examples/r2-fuse-mount/","name":"Mount R2 buckets with FUSE"}}]}```------title: Stateless Instancesdescription: Run multiple instances across Cloudflare's networkimage: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/examples/stateless.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Stateless Instances**Last reviewed:** 10 months agoRun multiple instances across Cloudflare's networkTo simply proxy requests to one of multiple instances of a container, you can use the `getRandom` function:TypeScript```import { Container, getRandom } from "@cloudflare/containers";const INSTANCE_COUNT = 3;class Backend extends Container { defaultPort = 8080; sleepAfter = "2h";}export default { async fetch(request: Request, env: Env): Promise<Response> { // note: "getRandom" to be replaced with latency-aware routing in the near future const containerInstance = await getRandom(env.BACKEND, INSTANCE_COUNT); return containerInstance.fetch(request); },};```Explain CodeNoteThis example uses the `getRandom` function, which is a temporary helper that will randomly select one of N instances of a Container to route requests to.In the future, we will provide improved latency-aware load balancing and autoscaling.This will make scaling stateless instances simple and routing more efficient. See the[autoscaling documentation](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/scaling-and-routing) for more details.```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/examples/stateless/","name":"Stateless Instances"}}]}```------title: Status Hooksdescription: Execute Workers code in reaction to Container status changesimage: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/examples/status-hooks.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Status Hooks**Last reviewed:** 10 months agoExecute Workers code in reaction to Container status changesWhen a Container starts, stops, becomes idle, and errors, it can trigger code execution in a Worker that has defined status hooks on the `Container` class. Refer to the [Container package docs ↗](https://github.com/cloudflare/containers/blob/main/README.md#lifecycle-hooks) for more details.TypeScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 4000; sleepAfter = "5m"; override onStart() { console.log("Container successfully started"); } override onStop(stopParams) { if (stopParams.exitCode === 0) { console.log("Container stopped gracefully"); } else { console.log("Container stopped with exit code:", stopParams.exitCode); } console.log("Container stop reason:", stopParams.reason); } override async onActivityExpired() { console.log("Container became idle, stopping it now"); await this.stop(); } override onError(error: string) { console.log("Container error:", error); }}```Explain Code```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/examples/status-hooks/","name":"Status Hooks"}}]}```------title: Websocket to Containerdescription: Forwarding a Websocket request to a Containerimage: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/examples/websocket.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Websocket to Container**Last reviewed:** 10 months agoForwarding a Websocket request to a ContainerWebSocket requests are automatically forwarded to a container using the default `fetch`method on the `Container` class:JavaScript```import { Container, getContainer } from "@cloudflare/containers";export class MyContainer extends Container { defaultPort = 8080; sleepAfter = "2m";}export default { async fetch(request, env) { // gets default instance and forwards websocket from outside Worker return getContainer(env.MY_CONTAINER).fetch(request); },};```Explain CodeView a full example in the [Container class repository ↗](https://github.com/cloudflare/containers/tree/main/examples/websocket).```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/examples/","name":"Examples"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/examples/websocket/","name":"Websocket to Container"}}]}```------title: Lifecycle of a Containerdescription: After you deploy an application with a Container, your image is uploaded toCloudflare's Registry and distributed globally to Cloudflare's Network.Cloudflare will pre-schedule instances and pre-fetch images across the globe to ensure quick starttimes when scaling up the number of concurrent container instances.image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/platform-details/architecture.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Lifecycle of a Container## DeploymentAfter you deploy an application with a Container, your image is uploaded to[Cloudflare's Registry](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/image-management) and distributed globally to Cloudflare's Network. Cloudflare will pre-schedule instances and pre-fetch images across the globe to ensure quick start times when scaling up the number of concurrent container instances.Unlike Workers, which are updated immediately on deploy, container instances are updated using a rolling deploy strategy. This allows you to gracefully shutdown any running instances during a rollout. Refer to [rollouts](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/rollouts/) for more details.## Lifecycle of a Request### Client to WorkerRecall that Containers are backed by [Durable Objects](https://backiee.wasmer.app/https_developers_cloudflare_com/durable-objects/) and [Workers](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/). Requests are first routed through a Worker, which is generally handled by a datacenter in a location with the best latency between itself and the requesting user. A different datacenter may be selected to optimize overall latency, if [Smart Placement](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/configuration/placement/)is on, or if the nearest location is under heavy load.Because all Container requests are passed through a Worker, end-users cannot make non-HTTP TCP or UDP requests to a Container instance. If you have a use case that requires inbound TCP or UDP from an end-user, please [let us know ↗](https://forms.gle/AGSq54VvUje6kmKu8).### Worker to Durable ObjectFrom the Worker, a request passes through a Durable Object instance (the [Container class](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/container-class/) extends a Durable Object class). Each Durable Object instance is a globally routable isolate that can execute code and store state. This allows developers to easily address and route to specific container instances (no matter where they are placed), define and run hooks on container status changes, execute recurring checks on the instance, and store persistent state associated with each instance.### Starting a ContainerWhen a Durable Object instance requests to start a new container instance, the **nearest location with a pre-fetched image** is selected.NoteCurrently, Durable Objects may be co-located with their associated Container instance, but often are not.Cloudflare is currently working on expanding the number of locations in which a Durable Object can run, which will allow container instances to always run in the same location as their Durable Object.Starting additional container instances will use other locations with pre-fetched images, and Cloudflare will automatically begin prepping additional machines behind the scenes for additional scaling and quick cold starts. Because there are a finite number of pre-warmed locations, some container instances may be started in locations that are farther away from the end-user. This is done to ensure that the container instance starts quickly. You are only charged for actively running instances and not for any unused pre-warmed images.#### Cold startsA cold start is when a container instance is started from a completely stopped state. If you call `env.MY_CONTAINER.get(id)` with a completely novel ID and launch this instance for the first time, it will result in a cold start. This will start the container image from its entrypoint for the first time. Depending on what this entrypoint does, it will take a variable amount of time to start.Container cold starts can often be the 2-3 second range, but this is dependent on image size and code execution time, among other factors.### Requests to running ContainersWhen a request _starts_ a new container instance, the nearest location with a pre-fetched image is selected. Subsequent requests to a particular instance, regardless of where they originate, will be routed to this location as long as the instance stays alive.However, once that container instance stops and restarts, future requests could be routed to a _different_ location. This location will again be the nearest location to the originating request with a pre-fetched image.### Container runtimeEach container instance runs inside its own VM, which provides strong isolation from other workloads running on Cloudflare's network. Containers should be built for the `linux/amd64` architecture, and should stay within[size limits](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/limits).[Logging](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/faq/#how-do-container-logs-work), metrics collection, and[networking](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/faq/#how-do-i-allow-or-disallow-egress-from-my-container) are automatically set up on each container, as configured by the developer.### Container shutdownIf you do not set [sleepAfter ↗](https://github.com/cloudflare/containers/blob/main/README.md#properties)on your Container class, or stop the instance manually, the container will shut down soon after the container stops receiving requests. By setting `sleepAfter`, the container will stay alive for approximately the specified duration.You can manually shutdown a container instance by calling `stop()` or `destroy()` on it - refer to the [Container package docs ↗](https://github.com/cloudflare/containers/blob/main/README.md#container-methods) for more details.When a container instance is going to be shut down, it is sent a `SIGTERM` signal, and then a `SIGKILL` signal after 15 minutes. You should perform any necessary cleanup to ensure a graceful shutdown in this time.#### Persistent diskAll disk is ephemeral. When a Container instance goes to sleep, the next time it is started, it will have a fresh disk as defined by its container image. Persistent disk is something the Cloudflare team is exploring in the future, but is not slated for the near term.## An example request* A developer deploys a Container. Cloudflare automatically readies instances across its Network.* A request is made from a client in Bariloche, Argentina. It reaches the Worker in a nearby Cloudflare location in Neuquen, Argentina.* This Worker request calls `getContainer(env.MY_CONTAINER, "session-1337")`. Under the hood, this brings up a Durable Object, which then calls `this.ctx.container.start`.* This requests the nearest free Container instance. Cloudflare recognizes that an instance is free in Buenos Aires, Argentina, and starts it there.* A different user needs to route to the same container. This user's request reaches the Worker running in Cloudflare's location in San Diego, US.* The Worker again calls `getContainer(env.MY_CONTAINER, "session-1337")`.* If the initial container instance is still running, the request is routed to the original location in Buenos Aires. If the initial container has gone to sleep, Cloudflare will once again try to find the nearest "free" instance of the Container, likely one in North America, and start an instance there.```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/platform-details/","name":"Platform Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/platform-details/architecture/","name":"Lifecycle of a Container"}}]}```------title: Lifecycle of a Containerdescription: After you deploy an application with a Container, your image is uploaded toCloudflare's Registry and distributed globally to Cloudflare's Network.Cloudflare will pre-schedule instances and pre-fetch images across the globe to ensure quick starttimes when scaling up the number of concurrent container instances.image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/platform-details/architecture.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Lifecycle of a Container## DeploymentAfter you deploy an application with a Container, your image is uploaded to[Cloudflare's Registry](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/image-management) and distributed globally to Cloudflare's Network. Cloudflare will pre-schedule instances and pre-fetch images across the globe to ensure quick start times when scaling up the number of concurrent container instances.Unlike Workers, which are updated immediately on deploy, container instances are updated using a rolling deploy strategy. This allows you to gracefully shutdown any running instances during a rollout. Refer to [rollouts](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/rollouts/) for more details.## Lifecycle of a Request### Client to WorkerRecall that Containers are backed by [Durable Objects](https://backiee.wasmer.app/https_developers_cloudflare_com/durable-objects/) and [Workers](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/). Requests are first routed through a Worker, which is generally handled by a datacenter in a location with the best latency between itself and the requesting user. A different datacenter may be selected to optimize overall latency, if [Smart Placement](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/configuration/placement/)is on, or if the nearest location is under heavy load.Because all Container requests are passed through a Worker, end-users cannot make non-HTTP TCP or UDP requests to a Container instance. If you have a use case that requires inbound TCP or UDP from an end-user, please [let us know ↗](https://forms.gle/AGSq54VvUje6kmKu8).### Worker to Durable ObjectFrom the Worker, a request passes through a Durable Object instance (the [Container class](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/container-class/) extends a Durable Object class). Each Durable Object instance is a globally routable isolate that can execute code and store state. This allows developers to easily address and route to specific container instances (no matter where they are placed), define and run hooks on container status changes, execute recurring checks on the instance, and store persistent state associated with each instance.### Starting a ContainerWhen a Durable Object instance requests to start a new container instance, the **nearest location with a pre-fetched image** is selected.NoteCurrently, Durable Objects may be co-located with their associated Container instance, but often are not.Cloudflare is currently working on expanding the number of locations in which a Durable Object can run, which will allow container instances to always run in the same location as their Durable Object.Starting additional container instances will use other locations with pre-fetched images, and Cloudflare will automatically begin prepping additional machines behind the scenes for additional scaling and quick cold starts. Because there are a finite number of pre-warmed locations, some container instances may be started in locations that are farther away from the end-user. This is done to ensure that the container instance starts quickly. You are only charged for actively running instances and not for any unused pre-warmed images.#### Cold startsA cold start is when a container instance is started from a completely stopped state. If you call `env.MY_CONTAINER.get(id)` with a completely novel ID and launch this instance for the first time, it will result in a cold start. This will start the container image from its entrypoint for the first time. Depending on what this entrypoint does, it will take a variable amount of time to start.Container cold starts can often be the 2-3 second range, but this is dependent on image size and code execution time, among other factors.### Requests to running ContainersWhen a request _starts_ a new container instance, the nearest location with a pre-fetched image is selected. Subsequent requests to a particular instance, regardless of where they originate, will be routed to this location as long as the instance stays alive.However, once that container instance stops and restarts, future requests could be routed to a _different_ location. This location will again be the nearest location to the originating request with a pre-fetched image.### Container runtimeEach container instance runs inside its own VM, which provides strong isolation from other workloads running on Cloudflare's network. Containers should be built for the `linux/amd64` architecture, and should stay within[size limits](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/limits).[Logging](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/faq/#how-do-container-logs-work), metrics collection, and[networking](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/faq/#how-do-i-allow-or-disallow-egress-from-my-container) are automatically set up on each container, as configured by the developer.### Container shutdownIf you do not set [sleepAfter ↗](https://github.com/cloudflare/containers/blob/main/README.md#properties)on your Container class, or stop the instance manually, the container will shut down soon after the container stops receiving requests. By setting `sleepAfter`, the container will stay alive for approximately the specified duration.You can manually shutdown a container instance by calling `stop()` or `destroy()` on it - refer to the [Container package docs ↗](https://github.com/cloudflare/containers/blob/main/README.md#container-methods) for more details.When a container instance is going to be shut down, it is sent a `SIGTERM` signal, and then a `SIGKILL` signal after 15 minutes. You should perform any necessary cleanup to ensure a graceful shutdown in this time.#### Persistent diskAll disk is ephemeral. When a Container instance goes to sleep, the next time it is started, it will have a fresh disk as defined by its container image. Persistent disk is something the Cloudflare team is exploring in the future, but is not slated for the near term.## An example request* A developer deploys a Container. Cloudflare automatically readies instances across its Network.* A request is made from a client in Bariloche, Argentina. It reaches the Worker in a nearby Cloudflare location in Neuquen, Argentina.* This Worker request calls `getContainer(env.MY_CONTAINER, "session-1337")`. Under the hood, this brings up a Durable Object, which then calls `this.ctx.container.start`.* This requests the nearest free Container instance. Cloudflare recognizes that an instance is free in Buenos Aires, Argentina, and starts it there.* A different user needs to route to the same container. This user's request reaches the Worker running in Cloudflare's location in San Diego, US.* The Worker again calls `getContainer(env.MY_CONTAINER, "session-1337")`.* If the initial container instance is still running, the request is routed to the original location in Buenos Aires. If the initial container has gone to sleep, Cloudflare will once again try to find the nearest "free" instance of the Container, likely one in North America, and start an instance there.```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/platform-details/","name":"Platform Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/platform-details/architecture/","name":"Lifecycle of a Container"}}]}```------title: Durable Object Interfaceimage: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/platform-details/durable-object-methods.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Durable Object Interface```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/platform-details/","name":"Platform Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/platform-details/durable-object-methods/","name":"Durable Object Interface"}}]}```------title: Environment Variablesdescription: The container runtime automatically sets the following variables:image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/platform-details/environment-variables.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Environment Variables## Runtime environment variablesThe container runtime automatically sets the following variables:* `CLOUDFLARE_APPLICATION_ID` \- the ID of the Containers application* `CLOUDFLARE_COUNTRY_A2` \- the [ISO 3166-1 Alpha 2 code ↗](https://www.iso.org/obp/ui/#search/code/) of a country the container is placed in* `CLOUDFLARE_LOCATION` \- a name of a location the container is placed in* `CLOUDFLARE_REGION` \- a region name* `CLOUDFLARE_DURABLE_OBJECT_ID` \- the ID of the Durable Object instance that the container is bound to. You can use this to identify particular container instances on the dashboard.## User-defined environment variablesYou can set environment variables when defining a Container in your Worker, or when starting a container instance.For example:JavaScript```class MyContainer extends Container { defaultPort = 4000; envVars = { MY_CUSTOM_VAR: "value", ANOTHER_VAR: "another_value", };}```More details about defining environment variables and secrets can be found in [this example](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/env-vars-and-secrets).```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/platform-details/","name":"Platform Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/platform-details/environment-variables/","name":"Environment Variables"}}]}```------title: Image Managementdescription: Learn how to use Cloudflare Registry, Docker Hub, and Amazon ECR images with Containers.image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/platform-details/image-management.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Image Management## Push images during `wrangler deploy`When running `wrangler deploy`, if you set the `image` attribute in your [Wrangler configuration](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/configuration/#containers) to a path to a Dockerfile, Wrangler will build your container image locally using Docker, then push it to a registry run by Cloudflare. This registry is integrated with your Cloudflare account and is backed by [R2](https://backiee.wasmer.app/https_developers_cloudflare_com/r2/). All authentication is handled automatically by Cloudflare both when pushing and pulling images.Just provide the path to your Dockerfile:* [ wrangler.jsonc ](#tab-panel-4125)* [ wrangler.toml ](#tab-panel-4126)JSONC```{ "containers": [ { "image": "./Dockerfile" } ]}```TOML```[[containers]]image = "./Dockerfile"```And deploy your Worker with `wrangler deploy`. No other image management is necessary.On subsequent deploys, Wrangler will only push image layers that have changed, which saves space and time.NoteDocker or a Docker-compatible CLI tool must be running for Wrangler to build and push images. This is not necessary if you are using a pre-built image, as described below.## Use pre-built container imagesContainers support images from the Cloudflare managed registry at `registry.cloudflare.com`, [Docker Hub ↗](https://hub.docker.com/), and [Amazon ECR ↗](https://aws.amazon.com/ecr/).NoteCloudflare does not cache images pulled from Docker Hub or Amazon ECR.Docker Hub pulls may be subject to Docker Hub pull limits or fair-use restrictions. Pulling images from Amazon ECR may incur AWS egress charges.### Use public Docker Hub imagesTo use a public Docker Hub image, set `image` to a fully qualified Docker Hub image reference in your Wrangler configuration.For example:* [ wrangler.jsonc ](#tab-panel-4127)* [ wrangler.toml ](#tab-panel-4128)JSONC```{ "containers": [ { "image": "docker.io/<NAMESPACE>/<REPOSITORY>:<TAG>" } ]}```TOML```[[containers]]image = "docker.io/<NAMESPACE>/<REPOSITORY>:<TAG>"```Public Docker Hub images do not require registry configuration.Private Docker Hub images use the private registry configuration flow described next.If Docker Hub credentials have been configured, those credentials are used to pull both public and private images.NoteOfficial Docker Hub images use the `library` namespace. For example, use `docker.io/library/<IMAGE>:<TAG>` instead of `docker.io/<IMAGE>:<TAG>`.### Configure private registry credentialsTo use a private image from Docker Hub or Amazon ECR, run [wrangler containers registries configure](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/commands/containers/#containers-registries-configure) for the registry domain.Wrangler prompts for the secret and stores it in [Secrets Store](https://backiee.wasmer.app/https_developers_cloudflare_com/secrets-store). If you do not already have a Secrets Store store, Wrangler prompts you to create one first.Use `--secret-name` to name or reuse a secret, `--secret-store-id` to target a specific Secrets Store store, and `--skip-confirmation` for non-interactive runs. In CI or scripts, pass the secret through `stdin`.### Use private Docker Hub imagesConfigure Docker Hub in Wrangler using these values:* registry domain: `docker.io`* username flag: `--dockerhub-username=<YOUR_DOCKERHUB_USERNAME>`* secret: Docker Hub personal access token with read-only accessTo create a Docker Hub personal access token:1. Sign in to [Docker Home ↗](https://app.docker.com/).2. Go to **Account settings** \> **Personal access tokens**.3. Select **Generate new token**.4. Give the token **Read** access, then copy the token value.Interactive: npm yarn pnpm```npx wrangler containers registries configure docker.io --dockerhub-username=<YOUR_DOCKERHUB_USERNAME>``````yarn wrangler containers registries configure docker.io --dockerhub-username=<YOUR_DOCKERHUB_USERNAME>``````pnpm wrangler containers registries configure docker.io --dockerhub-username=<YOUR_DOCKERHUB_USERNAME>```CI or scripts:Terminal window```printf '%s' "$DOCKERHUB_PAT" | npx wrangler containers registries configure docker.io --dockerhub-username=<YOUR_DOCKERHUB_USERNAME> --secret-name=<SECRET_NAME> --skip-confirmation```After you configure the registry, use the same fully qualified Docker Hub image reference shown above.### Use private Amazon ECR imagesConfigure Amazon ECR in Wrangler using these values:* registry domain: `<AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com`* access key flag: `--aws-access-key-id=<AWS_ACCESS_KEY_ID>`* secret: matching AWS secret access keyPublic ECR images are not supported. To generate the required credentials, create an IAM user with a read-only policy. The following example grants access to all image repositories in AWS account `123456789012` in `us-east-1`.```{ "Version": "2012-10-17", "Statement": [ { "Action": ["ecr:GetAuthorizationToken"], "Effect": "Allow", "Resource": "*" }, { "Effect": "Allow", "Action": [ "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:BatchGetImage" ], // arn:${Partition}:ecr:${Region}:${Account}:repository/${Repository-name} "Resource": [ "arn:aws:ecr:us-east-1:123456789012:repository/*" // "arn:aws:ecr:us-east-1:123456789012:repository/example-repo" ] } ]}```Explain CodeAfter you create the IAM user, use its credentials to [configure the registry in Wrangler](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/commands/containers/#containers-registries-configure). Wrangler prompts you to create a Secrets Store store if one does not already exist, then stores the secret there.Interactive: npm yarn pnpm```npx wrangler containers registries configure <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com --aws-access-key-id=<AWS_ACCESS_KEY_ID>``````yarn wrangler containers registries configure <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com --aws-access-key-id=<AWS_ACCESS_KEY_ID>``````pnpm wrangler containers registries configure <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com --aws-access-key-id=<AWS_ACCESS_KEY_ID>```CI or scripts:Terminal window```printf '%s' "$AWS_SECRET_ACCESS_KEY" | npx wrangler containers registries configure <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com --aws-access-key-id=<AWS_ACCESS_KEY_ID> --secret-name=<SECRET_NAME> --skip-confirmation```After you configure the registry, use the fully qualified Amazon ECR image reference in your Wrangler configuration:* [ wrangler.jsonc ](#tab-panel-4129)* [ wrangler.toml ](#tab-panel-4130)JSONC```{ "containers": [ { "image": "<AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/<REPOSITORY>:<TAG>" } ]}```TOML```[[containers]]image = "<AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/<REPOSITORY>:<TAG>"```### Use images from other registriesIf you want to use a pre-built image from another registry provider, first make sure it exists locally, then push it to the Cloudflare Registry:Terminal window```docker pull <PUBLIC_IMAGE>docker tag <PUBLIC_IMAGE> <IMAGE>:<TAG>```Wrangler provides a command to push images to the Cloudflare Registry: npm yarn pnpm```npx wrangler containers push <IMAGE>:<TAG>``````yarn wrangler containers push <IMAGE>:<TAG>``````pnpm wrangler containers push <IMAGE>:<TAG>```Or, you can use the `-p` flag with `wrangler containers build` to build and push an image in one step: npm yarn pnpm```npx wrangler containers build -p -t <TAG> .``````yarn wrangler containers build -p -t <TAG> .``````pnpm wrangler containers build -p -t <TAG> .```This will output an image registry URI that you can then use in your Wrangler configuration:* [ wrangler.jsonc ](#tab-panel-4131)* [ wrangler.toml ](#tab-panel-4132)JSONC```{ "containers": [ { "image": "registry.cloudflare.com/<YOUR_ACCOUNT_ID>/<IMAGE>:<TAG>" } ]}```TOML```[[containers]]image = "registry.cloudflare.com/<YOUR_ACCOUNT_ID>/<IMAGE>:<TAG>"```NoteWith `wrangler dev`, image references from the Cloudflare Registry, Docker Hub, and Amazon ECR are supported in local development.With `vite dev`, image references from external registries such as Docker Hub and Amazon ECR are supported, but `vite dev` cannot pull directly from the Cloudflare Registry.If you use a private Docker Hub or ECR image with `vite dev`, authenticate to that registry locally, for example with `docker login`.## Push images with CITo use an image built in a continuous integration environment, install `wrangler` then build and push images using either `wrangler containers build` with the `--push` flag, or using the `wrangler containers push` command.## Registry limitsImages are limited in size by available disk of the configured [instance type](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/limits/#instance-types) for a Container.Delete images with `wrangler containers images delete` to free up space, but reverting a Worker to a previous version that uses a deleted image will then error.```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/platform-details/","name":"Platform Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/platform-details/image-management/","name":"Image Management"}}]}```------title: Limits and Instance Typesdescription: The memory, vCPU, and disk space for Containers are set through instance types. You can use one of six predefined instance types or configure a custom instance type.image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/platform-details/limits.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Limits and Instance Types## Instance TypesThe memory, vCPU, and disk space for Containers are set through instance types. You can use one of six predefined instance types or configure a [custom instance type](#custom-instance-types).| Instance Type | vCPU | Memory | Disk || ------------- | ---- | ------- | ----- || lite | 1/16 | 256 MiB | 2 GB || basic | 1/4 | 1 GiB | 4 GB || standard-1 | 1/2 | 4 GiB | 8 GB || standard-2 | 1 | 6 GiB | 12 GB || standard-3 | 2 | 8 GiB | 16 GB || standard-4 | 4 | 12 GiB | 20 GB |These are specified using the [instance\_type property](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/configuration/#containers) in your Worker's Wrangler configuration file.NoteThe `dev` and `standard` instance types are preserved for backward compatibility and are aliases for `lite` and `standard-1`, respectively.### Custom Instance TypesIn addition to the predefined instance types, you can configure custom instance types by specifying `vcpu`, `memory_mib`, and `disk_mb` values. See the [Wrangler configuration documentation](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/configuration/#custom-instance-types) for configuration details.Custom instance types have the following constraints:| Resource | Limit || -------------------- | ---------------------------------- || Minimum vCPU | 1 || Maximum vCPU | 4 || Maximum Memory | 12 GiB || Maximum Disk | 20 GB || Memory to vCPU ratio | Minimum 3 GiB memory per vCPU || Disk to Memory ratio | Maximum 2 GB disk per 1 GiB memory |For workloads requiring less than 1 vCPU, use the predefined instance types such as `lite` or `basic`.Looking for larger instances? [Give us feedback here](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/beta-info/#feedback-wanted) and tell us what size instances you need, and what you want to use them for.## LimitsWhile in open beta, the following limits are currently in effect:| Feature | Workers Paid || --------------------------------------------------- | ---------------------------------------------- || Memory for all concurrent live Container instances | 6TiB || vCPU for all concurrent live Container instances | 1,500 || TB Disk for all concurrent live Container instances | 30TB || Image size | Same as [instance disk space](#instance-types) || Total image storage per account | 50 GB [1](#user-content-fn-1) |## Footnotes1. Delete container images with `wrangler containers delete` to free up space. If you delete a container image and then [roll back](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/configuration/versions-and-deployments/rollbacks/) your Worker to a previous version, this version may no longer work. [↩](#user-content-fnref-1)```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/platform-details/","name":"Platform Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/platform-details/limits/","name":"Limits and Instance Types"}}]}```------title: Handle outbound trafficdescription: Intercept and handle outbound HTTP from containers using Workers.image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/platform-details/outbound-traffic.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Handle outbound trafficOutbound handlers let you intercept and modify HTTP traffic from a container with trusted code.Use them to:* Allow or deny specific origin destinations* Safely inject authorization headers or tokens* Transparently reroute traffic* Add custom policy on outbound traffic (such as denying specific HTTP requests)* [Connect to Workers bindings](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/workers-connections/) like KV, R2, and Durable Objects## Block outbound trafficUse `enableInternet = false` to block public internet access by default:JavaScript```import { Container } from "@cloudflare/containers";export class MyContainer extends Container { enableInternet = false;}```When `enableInternet` is `false`, only traffic you explicitly allow later on this page through `allowedHosts` or outbound handlers can leave the container. Only ports `80`, `443`, and DNS are available, and DNS queries use Cloudflare's DNS servers.Note`enableInternet` takes effect when the container starts. Changes to `outbound` handlers and related outbound policies can affect a live-running container without restarting it.## Block or allow traffic by hostYou can filter outbound traffic with the `allowedHosts` and `deniedHosts` properties on the Container class.NoteExport `ContainerProxy` from your Worker entrypoint for outbound interception to work.When `allowedHosts` is set, it becomes a deny-by-default allowlist. Any host or IP not in the list is denied, and only matching destinations can reach `outbound` or `outboundByHost` handlers.`allowedHosts` and `deniedHosts` also support simple glob patterns where `*` matches any sequence of characters.By default, a Container will allow internet access, and you can set `deniedHosts` to disallow specific hosts or IPs:JavaScript```import { Container, ContainerProxy } from "@cloudflare/containers";export { ContainerProxy };export class MyContainer extends Container { // Make sure the container trusts /etc/cloudflare/certs/cloudflare-containers-ca.crt interceptHttps = true; deniedHosts = ["some-nefarious-website.com", "141.101.64.0/18"];}```You can also disable internet access by default, but allow specific hosts and IPs:JavaScript```import { Container, ContainerProxy } from "@cloudflare/containers";export { ContainerProxy };export class MyContainer extends Container { // Make sure the container trusts /etc/cloudflare/certs/cloudflare-containers-ca.crt interceptHttps = true; // default internet access to off unless overridden by 'allowedHosts' or outbound proxy enableInternet = false; // overrides enableInternet = false allowedHosts = ["allowed.com"];}```Explain Code## Define outbound handlersOutbound handlers are programmable egress proxies that run on the same machine as the container. They have access to all Workers bindings.Use `outbound` to intercept all HTTP and HTTPS traffic:JavaScript```import { Container, ContainerProxy } from "@cloudflare/containers";export { ContainerProxy };export class MyContainer extends Container { interceptHttps = true;}MyContainer.outbound = async (request, env, ctx) => { if (request.method !== "GET") { console.log(`Blocked ${request.method} to ${request.url}`); return new Response("Method Not Allowed", { status: 405 }); } return fetch(request);};```Explain CodeNoteHTTP requests to the outbound handler remain secure because they run on the same machine as the container. You can upgrade requests to HTTPS from the Worker itself to prevent plain-text traffic from reaching the internet.Use `outboundByHost` to map specific domain names or IP addresses to proxy functions:JavaScript```import { Container, ContainerProxy } from "@cloudflare/containers";export { ContainerProxy };export class MyContainer extends Container { interceptHttps = true;}MyContainer.outboundByHost = { "my.worker": async (request, env, ctx) => { // Run arbitrary Workers logic from this hostname return await someWorkersFunction(request.body); },};```Explain CodeCalls to `http://my.worker` from the container invoke the handler, which runs inside the Workers runtime, outside the container sandbox.`deniedHosts` and `allowedHosts` are evaluated before any outbound handler. If you use `allowedHosts`, include the hostname there for either `outbound` or `outboundByHost` to run. `outboundByHost` handlers take precedence over catch-all `outbound` handlers.## Securely inject credentialsBecause outbound handlers run in the Workers runtime — outside the container sandbox — they can hold secrets that the container itself never sees. The container makes a plain HTTP request, and the handler attaches the credential before forwarding it to the upstream service.JavaScript```export class MyContainer extends Container { // Make sure the container trusts /etc/cloudflare/certs/cloudflare-containers-ca.crt interceptHttps = true;}MyContainer.outboundByHost = { "github.com": (request, env, ctx) => { const requestWithAuth = new Request(request); requestWithAuth.headers.set("x-auth-token", env.SECRET); return fetch(requestWithAuth); },};```Explain CodeThis is especially useful for agentic workloads where you cannot fully trust the code running inside the container. With this pattern:* **No token is exposed to the container.** The secret lives in the Worker's environment and is never passed into the sandbox.* **No token rotation inside the container.** Rotate the secret in your Worker's environment and every request picks it up immediately.* **Per-host and per-instance rules.** Combine `outboundByHost` with `ctx.containerId` to scope credentials or permissions to a specific container instance.Here, `ctx.containerId` looks up a per-instance key from KV:JavaScript```export class MyContainer extends Container { // Make sure the container trusts /etc/cloudflare/certs/cloudflare-containers-ca.crt interceptHttps = true;}MyContainer.outboundByHost = { "my-internal-vcs.dev": async (request, env, ctx) => { const authKey = await env.KEYS.get(ctx.containerId); const requestWithAuth = new Request(request); requestWithAuth.headers.set("x-auth-token", authKey); return fetch(requestWithAuth); },};```Explain Code## HTTPS trafficBy default, HTTPS traffic is not intercepted by outbound handlers. To opt in you must set the `interceptHttps` attribute.JavaScript```export class MyContainer extends Container { // Make sure the container trusts /etc/cloudflare/certs/cloudflare-containers-ca.crt interceptHttps = true;}MyContainer.outbound = (req, env, ctx) => { // All HTTP(S) requests will trigger this hook. return fetch(req);};```This is useful for Sandbox-like services that redirect untrusted traffic from a container instance to Workers for filtering and modification.When HTTPS interception is active, an ephemeral CA file will be created at `/etc/cloudflare/certs/cloudflare-containers-ca.crt` once your container starts. The CA is only injected when you both set `interceptHttps = true` and define an `outbound` or `outboundByHost` handler.### Trust the CA certificateFor HTTPS interception to work, you must trust the CA file. The CA is ephemeral and only exists at runtime, so do not try to bake it into your image during `docker build`. Instead, copy it into your distro's trust store and refresh the trust store from the container `entrypoint` before your application starts.If your base image does not already include the trust-store tooling, install the distro's `ca-certificates` package in your image first.* [ Debian/Ubuntu ](#tab-panel-4133)* [ Alpine ](#tab-panel-4134)* [ Fedora/RHEL ](#tab-panel-4135)* [ Arch ](#tab-panel-4136)JavaScript```import { Container, ContainerProxy } from "@cloudflare/containers";export { ContainerProxy };export class MyContainer extends Container { interceptHttps = true; entrypoint = [ "sh", "-lc", [ "cp /etc/cloudflare/certs/cloudflare-containers-ca.crt /usr/local/share/ca-certificates/cloudflare-containers-ca.crt", "update-ca-certificates", "exec node server.js", ].join(" && "), ];}```Explain CodeJavaScript```import { Container, ContainerProxy } from "@cloudflare/containers";export { ContainerProxy };export class MyContainer extends Container { interceptHttps = true; entrypoint = [ "sh", "-lc", [ "cp /etc/cloudflare/certs/cloudflare-containers-ca.crt /usr/local/share/ca-certificates/cloudflare-containers-ca.crt", "update-ca-certificates", "exec node server.js", ].join(" && "), ];}```Explain CodeJavaScript```import { Container, ContainerProxy } from "@cloudflare/containers";export { ContainerProxy };export class MyContainer extends Container { interceptHttps = true; entrypoint = [ "sh", "-lc", [ "cp /etc/cloudflare/certs/cloudflare-containers-ca.crt /etc/pki/ca-trust/source/anchors/cloudflare-containers-ca.crt", "update-ca-trust", "exec node server.js", ].join(" && "), ];}```Explain CodeJavaScript```import { Container, ContainerProxy } from "@cloudflare/containers";export { ContainerProxy };export class MyContainer extends Container { interceptHttps = true; entrypoint = [ "sh", "-lc", [ "cp /etc/cloudflare/certs/cloudflare-containers-ca.crt /etc/ca-certificates/trust-source/anchors/cloudflare-containers-ca.crt", "trust extract-compat", "exec node server.js", ].join(" && "), ];}```Explain CodeReplace `node server.js` with the command that starts your application.Most runtimes will then trust the CA through the system root store automatically. If your runtime uses its own CA bundle, point it at `/etc/cloudflare/certs/cloudflare-containers-ca.crt` directly, for example with `NODE_EXTRA_CA_CERTS` or `REQUESTS_CA_BUNDLE`.NoteHTTP communication to the outbound handler is encrypted by the networking stack. For traffic that stays within the Cloudflare Developer Platform, plain HTTP is secure.## Non-HTTP trafficOutbound handlers only intercept HTTP and HTTPS traffic. Traffic on ports other than `80` and `443` is never routed through `outbound` or `outboundByHost`.If you set `enableInternet = false`, that traffic is denied. DNS queries are the one exception, but they only go to Cloudflare's DNS servers. That prevents using arbitrary DNS destinations for data exfiltration.## Change policies at runtimeUse `outboundHandlers` to define named handlers, then assign them to specific hosts at runtime using `setOutboundByHost()`. You can also apply a handler globally with `setOutboundHandler()`.You can also manage runtime policy with `setOutboundByHosts()`, `setAllowedHosts()`, `setDeniedHosts()`, `allowHost()`, `denyHost()`, `removeAllowedHost()`, and `removeDeniedHost()`.This lets a trusted Worker hold credentials without exposing them to an untrusted container:JavaScript```export class MyContainer extends Container { // Make sure the container trusts /etc/cloudflare/certs/cloudflare-containers-ca.crt interceptHttps = true;}MyContainer.outboundHandlers = { authenticatedGithub: async (request, env, ctx) => { const githubToken = env.GITHUB_TOKEN; return authenticateGitHttpsRequest(request, githubToken, ctx.containerId); },};```Explain CodeApply handlers to hosts programmatically from your Worker:JavaScript```async setUpContainer(req, env) { const container = await env.MY_CONTAINER.getByName("my-instance"); // Give the container access to github.com on a specific host during setup await container.setOutboundByHost("github.com", "authenticatedGithub"); // do something with github.com on your container...}async removeAccessToGithub(req, env) { const container = await env.MY_CONTAINER.getByName("my-instance"); // Remove access to Github await container.removeOutboundByHost("github.com");}```Explain Code## Handler precedenceRequests are evaluated in this order:1. `deniedHosts` is checked first. Matching hosts or IPs are denied immediately.2. `allowedHosts` is checked next. When it is set, any host or IP not in the list is denied. Matching hosts continue to outbound handlers, or egress to the public internet if no handler is set.3. Instance-level rules set with `setOutboundByHost()` are checked before class-level `outboundByHost` rules.4. Per-host handlers always take precedence over catch-all handlers, so `outboundByHost` runs before `outbound`.5. Instance-level handlers set with `setOutboundHandler()` are checked before the class-level `outbound` handler.6. If no handler matches, the request can still egress to the public internet when it matched `allowedHosts` or `enableInternet = true`. Otherwise, it is denied.## Low-level APITo configure outbound interception directly on `ctx.container`, use `interceptOutboundHttp` for a specific hostname glob, IP, or CIDR range, or `interceptAllOutboundHttp` for all traffic. Both accept a `WorkerEntrypoint`.JavaScript```import { WorkerEntrypoint } from "cloudflare:workers";export class MyOutboundWorker extends WorkerEntrypoint { fetch(request) { // Inspect, modify, or deny the request before passing it on return fetch(request); }}// Inside your Container DurableObjectthis.ctx.container.start({ enableInternet: false });const worker = this.ctx.exports.MyOutboundWorker({ props: {} });await this.ctx.container.interceptAllOutboundHttp(worker);```Explain CodeYou can call these methods before or after starting the container, and even while connections are open. In-flight TCP connections pick up the new handler automatically — no connections are dropped.JavaScript```// Intercept a specific CIDR rangeawait this.ctx.container.interceptOutboundHttp("203.0.113.0/24", worker);// Intercept by hostnamethis.ctx.container.interceptOutboundHttp("foo.com", worker);// Update the handler while the container is runningconst updated = this.ctx.exports.MyOutboundWorker({ props: { phase: "post-install" },});await this.ctx.container.interceptOutboundHttp("203.0.113.0/24", updated);```Explain CodeFor HTTPS, `interceptOutboundHttps` works the same way as `interceptOutboundHttp`.JavaScript```// Intercept a specific hostnamethis.ctx.container.interceptOutboundHttps("foo.com", worker);// Intercept all trafficthis.ctx.container.interceptOutboundHttps("*", worker);```The `Container` class calls these methods automatically when you use the functions shown above. You can also call them directly for cases the class does not cover.## Local development`wrangler dev` supports outbound interception. A sidecar process is spawned inside the container's network namespace. It applies `TPROXY` rules to route matching traffic to the local Workerd instance, mirroring production behavior.## Related resources* [Connect to Workers bindings](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/workers-connections/) — Access KV, R2, Durable Objects, and other bindings from a container* [Control outbound traffic (Sandboxes)](https://backiee.wasmer.app/https_developers_cloudflare_com/sandbox/guides/outbound-traffic/) — Sandbox SDK API for outbound handlers* [Environment variables and secrets](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/environment-variables/) — Configure secrets and environment variables* [Durable Object interface](https://backiee.wasmer.app/https_developers_cloudflare_com/durable-objects/api/container/) — Full `ctx.container` API reference```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/platform-details/","name":"Platform Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/platform-details/outbound-traffic/","name":"Handle outbound traffic"}}]}```------title: Rolloutsdescription: When you run wrangler deploy, the Worker code is updated immediately and Containerinstances are updated using a rolling deploy strategy. The default rollout configuration is two steps,where the first step updates 10% of the instances, and the second step updates the remaining 90%.This can be configured in your Wrangler config file using the rollout_step_percentage property.image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/platform-details/rollouts.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Rollouts## How rollouts workWhen you run `wrangler deploy`, the Worker code is updated immediately and Container instances are updated using a rolling deploy strategy. The default rollout configuration is two steps, where the first step updates 10% of the instances, and the second step updates the remaining 90%. This can be configured in your Wrangler config file using the [rollout\_step\_percentage](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/configuration#containers) property.When deploying a change, you can also configure a [rollout\_active\_grace\_period](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/configuration#containers), which is the minimum number of seconds to wait before an active container instance becomes eligible for updating during a rollout. At that point, the container will be sent at `SIGTERM`, and still has 15 minutes to shut down gracefully. If the instance does not stop within 15 minutes, it is forcefully stopped with a `SIGKILL` signal. If you have cleanup that must occur before a Container instance is stopped, you should do it during this 15 minute period.Once stopped, the instance is replaced with a new instance running the updated code. Requests may hang while the container is starting up again.Here is an example configuration that sets a 5 minute grace period and a two step rollout where the first step updates 10% of instances and the second step updates 100% of instances:* [ wrangler.jsonc ](#tab-panel-4137)* [ wrangler.toml ](#tab-panel-4138)JSONC```{ "containers": [ { "max_instances": 10, "class_name": "MyContainer", "image": "./Dockerfile", "rollout_active_grace_period": 300, "rollout_step_percentage": [ 10, 100 ] } ], "durable_objects": { "bindings": [ { "name": "MY_CONTAINER", "class_name": "MyContainer" } ] }, "migrations": [ { "tag": "v1", "new_sqlite_classes": [ "MyContainer" ] } ]}```Explain CodeTOML```[[containers]]max_instances = 10class_name = "MyContainer"image = "./Dockerfile"rollout_active_grace_period = 300rollout_step_percentage = [ 10, 100 ][[durable_objects.bindings]]name = "MY_CONTAINER"class_name = "MyContainer"[[migrations]]tag = "v1"new_sqlite_classes = [ "MyContainer" ]```Explain Code## Immediate rolloutsIf you need to do a one-off deployment that rolls out to 100% of container instances in one step, you can deploy with: npm yarn pnpm```npx wrangler deploy --containers-rollout=immediate``````yarn wrangler deploy --containers-rollout=immediate``````pnpm wrangler deploy --containers-rollout=immediate```Note that `rollout_active_grace_period`, if configured, will still apply.```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/platform-details/","name":"Platform Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/platform-details/rollouts/","name":"Rollouts"}}]}```------title: Scaling and Routingdescription: Currently, Containers are only scaled manually by getting containers with a unique ID, thenstarting the container. Note that getting a container does not automatically start it.image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/platform-details/scaling-and-routing.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Scaling and Routing### Scaling container instances with `get()`NoteThis section uses helpers from the [Container class](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/container-class/).Currently, Containers are only scaled manually by getting containers with a unique ID, then starting the container. Note that getting a container does not automatically start it.TypeScript```// get and start two container instancesconst containerOne = getContainer( env.MY_CONTAINER, idOne,).startAndWaitForPorts();const containerTwo = getContainer( env.MY_CONTAINER, idTwo,).startAndWaitForPorts();```Explain CodeEach instance will run until its `sleepAfter` time has elapsed, or until it is manually stopped.This behavior is very useful when you want explicit control over the lifecycle of container instances. For instance, you may want to spin up a container backend instance for a specific user, or you may briefly run a code sandbox to isolate AI-generated code, or you may want to run a short-lived batch job.#### The `getRandom` helper functionHowever, sometimes you want to run multiple instances of a container and easily route requests to them.Currently, the best way to achieve this is with the _temporary_ `getRandom` helper function:JavaScript```import { Container, getRandom } from "@cloudflare/containers";const INSTANCE_COUNT = 3;class Backend extends Container { defaultPort = 8080; sleepAfter = "2h";}export default { async fetch(request: Request, env: Env): Promise<Response> { // note: "getRandom" to be replaced with latency-aware routing in the near future const containerInstance = getRandom(env.BACKEND, INSTANCE_COUNT) return containerInstance.fetch(request); },};```Explain CodeWe have provided the getRandom function as a stopgap solution to route to multiple stateless container instances. It will randomly select one of N instances for each request and route to it. Unfortunately, it has two major downsides:* It requires that the user set a fixed number of instances to route to.* It will randomly select each instance, regardless of location.We plan to fix these issues with built-in autoscaling and routing features in the near future.```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/platform-details/","name":"Platform Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/platform-details/scaling-and-routing/","name":"Scaling and Routing"}}]}```------title: Connect to Workers and Bindingsdescription: Access KV, R2, Durable Objects, and other bindings from a container.image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/platform-details/workers-connections.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Connect to Workers and BindingsContainers can access [Workers bindings](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/runtime-apis/bindings/) — KV, R2, D1, Durable Objects, and others — through [outbound handlers](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/outbound-traffic/#define-outbound-handlers). An outbound handler intercepts HTTP requests from the container and runs inside the Workers runtime, where all of your configured bindings are available.The container makes a plain HTTP request to a virtual hostname (for example, `http://my.kv/some-key`), and the outbound handler resolves it using the bound resource. No SDK or client library is required inside the container.## Use bindings in outbound handlersDefine an `outboundByHost` handler for each virtual hostname. The `env` argument gives you access to every binding declared in your Wrangler configuration.JavaScript```export class MyContainer extends Container {}MyContainer.outboundByHost = { "my.kv": async (request, env, ctx) => { const url = new URL(request.url); const key = url.pathname.slice(1); const value = await env.KV.get(key); return new Response(value); }, "my.r2": async (request, env, ctx) => { const url = new URL(request.url); // Scope access to this container's ID const path = `${ctx.containerId}${url.pathname}`; const object = await env.R2.get(path); return new Response(object?.body ?? null, { status: object ? 200 : 404 }); },};```Explain CodeThe container calls `http://my.kv/some-key` and the handler resolves it using the KV binding. A call to `http://my.r2/file.png` reads from R2, scoped to the current container instance.NoteYou can use `ctx.containerId` to apply different rules per container instance — for example, to look up per-instance configuration from KV.## Access Durable Object stateThe `ctx` argument exposes `containerId`, which lets you interact with the container's own Durable Object from an outbound handler.JavaScript```"get-state.do": async (request, env, ctx) => { const id = env.MY_CONTAINER.idFromString(ctx.containerId); const stub = env.MY_CONTAINER.get(id); // Assumes getStateForKey is defined on your DO return stub.getStateForKey(request.body);},```## Related resources* [Handle outbound traffic](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/outbound-traffic/) — Block, allow, and intercept all outbound HTTP from a container* [Environment variables and secrets](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/environment-variables/) — Configure secrets and environment variables* [Durable Object interface](https://backiee.wasmer.app/https_developers_cloudflare_com/durable-objects/api/container/) — Full `ctx.container` API reference```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/platform-details/","name":"Platform Reference"}},{"@type":"ListItem","position":4,"item":{"@id":"/containers/platform-details/workers-connections/","name":"Connect to Workers and Bindings"}}]}```------title: Local Developmentdescription: Learn how to run Container-enabled Workers locally with `wrangler dev` and `vite dev`.image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/local-dev.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Local DevelopmentYou can run both your container and your Worker locally by simply running [npx wrangler dev](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/commands/general/#dev) (or `vite dev` for Vite projects using the [Cloudflare Vite plugin](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/vite-plugin/)) in your project's directory.To develop Container-enabled Workers locally, you will need to first ensure that a Docker compatible CLI tool and Engine are installed. For instance, you could use [Docker Desktop ↗](https://docs.docker.com/desktop/) or [Colima ↗](https://github.com/abiosoft/colima).When you start a dev session, your container image will be built or downloaded. If your[Wrangler configuration](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/configuration/#containers) sets the `image` attribute to a local path, the image will be built using the local Dockerfile. If the `image` attribute is set to an image reference, the image will be pulled from the referenced registry, such as the Cloudflare Registry, Docker Hub, or Amazon ECR.NoteWith `wrangler dev`, image references from the Cloudflare Registry, Docker Hub, and Amazon ECR are supported in local development.With `vite dev`, image references from external registries such as Docker Hub and Amazon ECR are supported, but `vite dev` cannot pull directly from the Cloudflare Registry.If you use a private Docker Hub or ECR image with `vite dev`, authenticate to that registry locally, for example with `docker login`.As a workaround for Cloudflare Registry images, point `vite dev` at a local Dockerfile that uses `FROM <IMAGE_REFERENCE>`. Docker then pulls the base image during the local build. Make sure to `EXPOSE` a port for local dev as well.Container instances will be launched locally when your Worker code calls to create a new container. Requests will then automatically be routed to the correct locally-running container.When the dev session ends, all associated container instances should be stopped, but local images are not removed, so that they can be reused in subsequent builds.NoteIf your Worker app creates many container instances, your local machine may not be able to run as many containers concurrently as is possible when you deploy to Cloudflare.Also, `max_instances` configuration option does not apply during local development.Additionally, if you regularly rebuild containers locally, you may want to clear out old container images (using `docker image prune` or similar) to reduce disk used.## Iterating on Container codeWhen you develop with Wrangler or Vite, your Worker's code is automatically reloaded each time you save a change, but code running within the container is not.To rebuild your container with new code changes, you can hit the `[r]` key on your keyboard, which triggers a rebuild. Container instances will then be restarted with the newly built images.You may prefer to set up your own code watchers and reloading mechanisms, or mount a local directory into the local container images to sync code changes. This can be done, but there is no built-in mechanism for doing so, and best-practices will depend on the languages and frameworks you are using in your container code.## Troubleshooting### Exposing PortsIn production, all of your container's ports will be accessible by your Worker, so you do not need to specifically expose ports using the [EXPOSE instruction ↗](https://docs.docker.com/reference/dockerfile/#expose) in your Dockerfile.But for local development you will need to declare any ports you need to access in your Dockerfile with the EXPOSE instruction; for example: `EXPOSE 4000`, if you will be accessing port 4000.If you have not exposed any ports, you will see the following error in local development:```The container "MyContainer" does not expose any ports. In your Dockerfile, please expose any ports you intend to connect to.```And if you try to connect to any port that you have not exposed in your `Dockerfile` you will see the following error:```connect(): Connection refused: container port not found. Make sure you exposed the port in your container definition.```You may also see this while the container is starting up and no ports are available yet. You should retry until the ports become available. This retry logic should be handled for you if you are using the [containers package ↗](https://github.com/cloudflare/containers/tree/main/src).### Socket configuration - `internal error`If you see an opaque `internal error` when attempting to connect to your container, you may need to set the `DOCKER_HOST` environment variable to the socket path your container engine is listening on. Wrangler or Vite will attempt to automatically find the correct socket to use to communicate with your container engine, but if that does not work, you may have to set this environment variable to the appropriate socket path.### SSL errors with the Cloudflare One Client or a VPNIf you are running the Cloudflare One Client or a VPN that performs TLS inspection, HTTPS requests made during the Docker build process may fail with SSL or certificate errors. This happens because the VPN intercepts HTTPS traffic and re-signs it with its own certificate authority, which Docker does not trust by default.To resolve this, you can either:* Disable the Cloudflare One Client or your VPN while running `wrangler dev` or `wrangler deploy`, then re-enable it afterwards.* Add the certificate to your Docker build context. The Cloudflare One Client exposes its certificate via the `NODE_EXTRA_CA_CERTS` and `SSL_CERT_FILE` environment variables on your host machine. You can pass the certificate into your Docker build as an environment variable, so that it is available during the build without being baked into the final image.```RUN if [ -n "$SSL_CERT_FILE" ]; then \ cp "$SSL_CERT_FILE" /usr/local/share/ca-certificates/Custom_CA.crt && \ update-ca-certificates; \ fi```NoteThe above Dockerfile snippet is an example. Depending on your base image, the commands to install certificates may differ (for example, Alpine uses `apk add ca-certificates` and a different certificate path).This snippet will store the certificate into the image. Depending on whether your production environment needs the certificate, you may choose to do this only during development or use it in production too.Wrangler invokes Docker automatically when you run `wrangler dev` or `wrangler deploy`, so if you need to pass build secrets, you will need to build and push the image manually using `wrangler containers push`.```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/local-dev/","name":"Local Development"}}]}```------title: Wrangler Configurationimage: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/wrangler-configuration.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Wrangler Configuration```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/wrangler-configuration/","name":"Wrangler Configuration"}}]}```------title: Wrangler Commandsimage: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/wrangler-commands.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Wrangler Commands```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/wrangler-commands/","name":"Wrangler Commands"}}]}```------title: Beta Info &#38; Roadmapdescription: Currently, Containers are in beta. There are several changes we plan to make prior to GA:image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/beta-info.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Beta Info & RoadmapCurrently, Containers are in beta. There are several changes we plan to make prior to GA:## Upcoming Changes and Known Gaps### LimitsContainer limits will be raised in the future. We plan to increase both maximum instance size and maximum number of instances in an account.See the [Limits documentation](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/#limits) for more information.### Autoscaling and load balancingCurrently, Containers are not autoscaled or load balanced. Containers can be scaled manually by calling `get()` on their binding with a unique ID.We plan to add official support for utilization-based autoscaling and latency-aware load balancing in the future.See the [Autoscaling documentation](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/scaling-and-routing) for more information.### Reduction of log noiseCurrently, the `Container` class uses Durable Object alarms to help manage Container shutdown. This results in unnecessary log noise in the Worker logs. You can filter these logs out in the dashboard by adding a Query, but this is not ideal.We plan to automatically reduce log noise in the future.### Dashboard UpdatesThe dashboard will be updated to show:* links from Workers to their associated Containers### Co-locating Durable Objects and ContainersCurrently, Durable Objects are not co-located with their associated Container. When requesting a container, the Durable Object will find one close to it, but not on the same machine.We plan to co-locate Durable Objects with their Container in the future.### More advanced Container placementWe currently prewarm servers across our global network with container images to ensure quick start times. There are times in which you may request a new container and it will be started in a location that farther from the end user than is desired. We are optimizing this process to ensure that this happens as little as possible, but it may still occur.### Atomic code updates across Workers and ContainersWhen deploying a Container with `wrangler deploy`, the Worker code will be immediately updated while the Container code will slowly be updated using a rolling deploy.This means that you must ensure Worker code is backwards compatible with the old Container code.In the future, Worker code in the Durable Object will only update when associated Container code updates.## Feedback wantedThere are several areas where we wish to gather feedback from users:* Do you want to integrate Containers with any other Cloudflare services? If so, which ones and how?* Do you want more ways to interact with a Container via Workers? If so, how?* Do you need different mechanisms for routing requests to containers?* Do you need different mechanisms for scaling containers? (see [scaling documentation](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/scaling-and-routing) for information on autoscaling plans)At any point during the Beta, feel free to [give feedback using this form ↗](https://forms.gle/CscdaEGuw5Hb6H2s7).```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/beta-info/","name":"Beta Info & Roadmap"}}]}```------title: Frequently Asked Questionsdescription: Frequently Asked Questions:image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/faq.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Frequently Asked QuestionsFrequently Asked Questions:## How do Container logs work?To get logs in the Dashboard, including live tailing of logs, toggle `observability` to true in your Worker's wrangler config:* [ wrangler.jsonc ](#tab-panel-4121)* [ wrangler.toml ](#tab-panel-4122)JSONC```{ "observability": { "enabled": true }}```TOML```[observability]enabled = true```Logs are subject to the same [limits as Worker logs](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/observability/logs/workers-logs/#limits), which means that they are retained for 3 days on Free plans and 7 days on Paid plans.See [Workers Logs Pricing](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/observability/logs/workers-logs/#pricing) for details on cost.If you are an Enterprise user, you are able to export container logs via [Logpush](https://backiee.wasmer.app/https_developers_cloudflare_com/logs/logpush/)to your preferred destination.## How are container instance locations selected?When initially deploying a Container, Cloudflare will select various locations across our network to deploy instances to. These locations will span multiple regions.When a Container instance is requested with `this.ctx.container.start`, the nearest free container instance will be selected from the pre-initialized locations. This will likely be in the same region as the external request, but may not be. Once the container instance is running, any future requests will be routed to the initial location.An Example:* A user deploys a Container. Cloudflare automatically readies instances across its Network.* A request is made from a client in Bariloche, Argentia. It reaches the Worker in Cloudflare's location in Neuquen, Argentina.* This Worker request calls `MY_CONTAINER.get("session-1337")` which brings up a Durable Object, which then calls `this.ctx.container.start`.* This requests the nearest free Container instance.* Cloudflare recognizes that an instance is free in Buenos Aires, Argentina, and starts it there.* A different user needs to route to the same container. This user's request reaches the Worker running in Cloudflare's location in San Diego.* The Worker again calls `MY_CONTAINER.get("session-1337")`.* If the initial container instance is still running, the request is routed to the location in Buenos Aires. If the initial container has gone to sleep, Cloudflare will once again try to find the nearest "free" instance of the Container, likely one in North America, and start an instance there.## How do container updates and rollouts work?See [rollout documentation](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/rollouts/) for details.## How does scaling work?See [scaling & routing documentation](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/scaling-and-routing/) for details.## What are cold starts? How fast are they?A cold start is when a container instance is started from a completely stopped state.If you call `env.MY_CONTAINER.get(id)` with a completely novel ID and launch this instance for the first time, it will result in a cold start.This will start the container image from its entrypoint for the first time. Depending on what this entrypoint does, it will take a variable amount of time to start.Container cold starts can often be the 2-3 second range, but this is dependent on image size and code execution time, among other factors.## How do I use an existing container image?See [image management documentation](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/image-management/#use-pre-built-container-images) for details.## Is disk persistent? What happens to my disk when my container sleeps?All disk is ephemeral. When a Container instance goes to sleep, the next time it is started, it will have a fresh disk as defined by its container image.Persistent disk is something the Cloudflare team is exploring in the future, but is not slated for the near term.## What happens if I run out of memory?If you run out of memory, your instance will throw an Out of Memory (OOM) error and will be restarted.Containers do not use swap memory.## How long can instances run for? What happens when a host server is shutdown?Cloudflare will not actively shut off a container instance after a specific amount of time. If you do not set `sleepAfter` on your Container class, or stop the instance manually, it will continue to run unless its host server is restarted. This happens on an irregular cadence, but frequently enough where Cloudflare does not guarantee that any instance will run for any set period of time.When a container instance is going to be shut down, it is sent a `SIGTERM` signal, and then a `SIGKILL` signal after 15 minutes. You should perform any necessary cleanup to ensure a graceful shutdown in this time. The container instance will be rebooted elsewhere shortly after this.## How can I pass secrets to my container?You can use [Worker Secrets](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/configuration/secrets/) or the [Secrets Store](https://backiee.wasmer.app/https_developers_cloudflare_com/secrets-store/integrations/workers/)to define secrets for your Workers.Then you can pass these secrets to your Container using the `envVars` property:JavaScript```class MyContainer extends Container { defaultPort = 5000; envVars = { MY_SECRET: this.env.MY_SECRET, };}```Or when starting a Container instance on a Durable Object:JavaScript```this.ctx.container.start({ env: { MY_SECRET: this.env.MY_SECRET, },});```See [the Env Vars and Secrets Example](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/examples/env-vars-and-secrets/) for details.## Can I run Docker inside a container (Docker-in-Docker)?Yes. Use the `docker:dind-rootless` base image since Containers run without root privileges.You must disable iptables when starting the Docker daemon because Containers do not support iptables manipulation:Dockerfile```FROM docker:dind-rootless# Start dockerd with iptables disabled, then run your appENTRYPOINT ["sh", "-c", "dockerd-entrypoint.sh dockerd --iptables=false --ip6tables=false & exec /path/to/your-app"]```If your application needs to wait for dockerd to become ready before using Docker, use an entrypoint script instead of the inline command above:entrypoint.sh```#!/bin/shset -eu# Wait for dockerd to be readyuntil docker version >/dev/null 2>&1; do sleep 0.2doneexec /path/to/your-app```Working with disabled iptablesCloudflare Containers do not support iptables manipulation. The `--iptables=false` and `--ip6tables=false` flags prevent Docker from attempting to configure network rules, which would otherwise fail.To send or receive traffic from a container running within Docker-in-Docker, use the `--network=host` flag when running Docker commands.This allows you to connect to the container, but it means each inner container has access to your outer container's network stack. Ensure you understand the security implications of this setup before proceeding.For a complete working example, see the [Docker-in-Docker Containers example ↗](https://github.com/th0m/containers-dind).## How do I allow or disallow egress from my container?When booting a Container, you can specify `enableInternet`, which will toggle internet access on or off.To disable it, configure it on your Container class:JavaScript```class MyContainer extends Container { defaultPort = 7000; enableInternet = false;}```or when starting a Container instance on a Durable Object:JavaScript```this.ctx.container.start({ enableInternet: false,});``````json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/faq/","name":"Frequently Asked Questions"}}]}```------title: SSHdescription: Connect to running container instances with SSH.image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/ssh.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# SSHAnyone with write access to a Container can SSH into it with Wrangler as long as SSH is enabled.## Configure SSHSSH can be configured in your [Container's configuration](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/configuration/#containers) with the `wrangler_ssh` and `authorized_keys` properties. Only the `ssh-ed25519` key type is supported.The `wrangler_ssh.enabled` property only controls whether you can SSH into a Container through Wrangler. If `wrangler_ssh.enabled` is false but keys are still present in `authorized_keys`, the SSH service will still be started on the Container.## Connect with WranglerTo SSH into a Container with Wrangler, you must first enable Wrangler SSH. The following example shows a basic configuration:* [ wrangler.jsonc ](#tab-panel-4139)* [ wrangler.toml ](#tab-panel-4140)JSONC```{ "containers": [ { // other options here... "wrangler_ssh": { "enabled": true }, "authorized_keys": [ { "name": "<NAME>", "public_key": "<YOUR_PUBLIC_KEY_HERE>" } ] } ]}```Explain CodeTOML```[[containers]][containers.wrangler_ssh]enabled = true[[containers.authorized_keys]]name = "<NAME>"public_key = "<YOUR_PUBLIC_KEY_HERE>"```For more information on configuring SSH, refer to [Wrangler SSH configuration](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/configuration/#wrangler-ssh).Find the instance ID for your Container by running [wrangler containers instances](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/wrangler/commands/containers/#containers-instances) or in the [Cloudflare dashboard ↗](https://dash.cloudflare.com/?to=/:account/workers/containers). The instance you want to SSH into must be running. SSH will not start a stopped Container, and an active SSH connection alone will not keep a Container alive.Once SSH is configured and the Container is running, open the SSH connection with:Terminal window```wrangler containers ssh <INSTANCE_ID>```## Process visibilityWithout the [containers\_pid\_namespace](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/configuration/compatibility-flags/#use-an-isolated-pid-namespace-for-containers) compatibility flag, all processes inside the VM are visible when you connect to your Container through SSH. This flag is turned on by default for Workers with a [compatibility date](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/configuration/compatibility-dates/) of `2026-04-01` or later.```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/ssh/","name":"SSH"}}]}```------title: Pricingdescription: Containers are billed for every 10ms that they are actively running at the following rates, with included monthly usage as part of the $5 USD per month Workers Paid plan:image: https://backiee.wasmer.app/https_developers_cloudflare_com/dev-products-preview.png---[Skip to content](#%5Ftop)Was this helpful?YesNo[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/containers/pricing.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose)Copy page# Pricing## vCPU, Memory and DiskContainers are billed for every 10ms that they are actively running at the following rates, with included monthly usage as part of the $5 USD per month [Workers Paid plan](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/platform/pricing/):| Memory | CPU | Disk | || ---------------- | ------------------------------------------------------------------ | -------------------------------------------------------------- | --------------------------------------------------------- || **Free** | N/A | N/A | || **Workers Paid** | 25 GiB-hours/month included +$0.0000025 per additional GiB-second | 375 vCPU-minutes/month \+ $0.000020 per additional vCPU-second | 200 GB-hours/month +$0.00000007 per additional GB-second |You only pay for what you use — charges start when a request is sent to the container or when it is manually started. Charges stop after the container instance goes to sleep, which can happen automatically after a timeout. This makes it easy to scale to zero, and allows you to get high utilization even with bursty traffic.Memory and disk usage are based on the _provisioned resources_ for the instance type you select, while CPU usage is based on _active usage_ only.#### Instance TypesWhen you deploy a container, you specify an [instance type](https://backiee.wasmer.app/https_developers_cloudflare_com/containers/platform-details/#instance-types).The instance type you select will impact your bill — larger instances include more memory and disk, incurring additional costs, and higher CPU capacity, which allows you to incur higher CPU costs based on active usage.The following instance types are currently available:| Instance Type | vCPU | Memory | Disk || ------------- | ---- | ------- | ----- || lite | 1/16 | 256 MiB | 2 GB || basic | 1/4 | 1 GiB | 4 GB || standard-1 | 1/2 | 4 GiB | 8 GB || standard-2 | 1 | 6 GiB | 12 GB || standard-3 | 2 | 8 GiB | 16 GB || standard-4 | 4 | 12 GiB | 20 GB |## Network EgressEgress from Containers is priced at the following rates:| Region | Price per GB | Included Allotment per month || ---------------------- | ------------ | ---------------------------- || North America & Europe | $0.025 | 1 TB || Oceania, Korea, Taiwan | $0.05 | 500 GB || Everywhere Else | $0.04 | 500 GB |## Workers and Durable Objects PricingWhen you use Containers, incoming requests to your containers are handled by your [Worker](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/platform/pricing/), and each container has its own[Durable Object](https://backiee.wasmer.app/https_developers_cloudflare_com/durable-objects/platform/pricing/). You are billed for your usage of both Workers and Durable Objects.## Logs and ObservabilityContainers are integrated with the [Workers Logs](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/observability/logs/workers-logs/) platform, and billed at the same rate. Refer to [Workers Logs pricing](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/observability/logs/workers-logs/#pricing) for details.When you [enable observability for your Worker](https://backiee.wasmer.app/https_developers_cloudflare_com/workers/observability/logs/workers-logs/#enable-workers-logs) with a binding to a container, logs from your container will show in both the Containers and Observability sections of the Cloudflare dashboard.```json{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/containers/","name":"Containers"}},{"@type":"ListItem","position":3,"item":{"@id":"/containers/pricing/","name":"Pricing"}}]}```