---
title: Containers
description: Run serverless containers alongside Workers to handle resource-intensive workloads, custom runtimes, and existing container images on Cloudflare.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Containers

Enhance your Workers with serverless containers

 Available on Workers Paid plan 

Run code written in any programming language, built for any runtime, as part of apps built on [Workers](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://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 images

Container instances are spun up on-demand and controlled by code you write in your [Worker](https://developers.cloudflare.com/workers). Instead of chaining together API calls or writing Kubernetes operators, you just write JavaScript:

* [ Worker Code ](#tab-panel-5698)
* [ Worker Config ](#tab-panel-5699)

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);

  },

};


```

* [  wrangler.jsonc ](#tab-panel-5696)
* [  wrangler.toml ](#tab-panel-5697)

JSONC

```

{

  "name": "container-starter",

  "main": "src/index.js",

  // Set this to today's date

  "compatibility_date": "2026-05-20",

  "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"

    }

  ]

}


```

TOML

```

name = "container-starter"

main = "src/index.js"

# Set this to today's date

compatibility_date = "2026-05-20"


[[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"


```

[ Get started ](https://developers.cloudflare.com/containers/get-started/) [ Containers dashboard ](https://dash.cloudflare.com/?to=/:account/workers/containers) 

---

## Next Steps

###  Deploy your first Container 

Build and push an image, call a Container from a Worker, and understand scaling and routing.

[ Deploy a Container ](https://developers.cloudflare.com/containers/get-started/) 

###  Container Examples 

See 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://developers.cloudflare.com/containers/examples/) 

---

## More resources

[Wrangler](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://developers.cloudflare.com/containers/platform-details/limits/) 

Learn about what limits Containers have and how to work within them.

[SSH](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 started
description: Deploy your first Container on Cloudflare by building an image, configuring a Worker, and routing requests to container instances.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Getting started

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.

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 locally

In 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 Container

Run 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://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://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 container

The 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/).

Note

After 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 status

After deploying, run the following command to show a list of 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 Containers

Now, 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 balance requests to one of 3 containers chosen at random.

You can confirm this behavior by reading the output of each request.

## Understanding the Code

Now 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 Object

Incoming requests are initially handled by the Worker, then passed to a container-enabled [Durable Object](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://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://developers.cloudflare.com/durable-objects/api/container/) and the[Container class repository ↗](https://github.com/cloudflare/containers) for more details.

### Configuration

Your [Wrangler configuration file](https://developers.cloudflare.com/workers/wrangler/configuration/) defines the configuration for both your Worker and your container:

* [  wrangler.jsonc ](#tab-panel-5748)
* [  wrangler.toml ](#tab-panel-5749)

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"],

    },

  ],

}


```

TOML

```

[[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" ]


```

Important 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://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://developers.cloudflare.com/durable-objects/best-practices/access-durable-objects-storage/#create-sqlite-backed-durable-object-class) not `new_classes`.

### The Container Image

Your 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://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)

}


```

Note

After deploying the example code, to deploy a different image, you can replace the provided image with one of your own.

### Worker code

#### Container Configuration

First 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);

  }

}


```

This 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://developers.cloudflare.com/containers/container-class/) for more details and configuration options.

#### Routing to Containers

When a request enters Cloudflare, your Worker's [fetch handler](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.

Note

Today, routing requests to one of many interchangeable Container instances uses the`getRandom` helper.

It randomly selects one of a fixed number of instances for each request.

## View Containers in your Dashboard

The [Containers Dashboard ↗](https://dash.cloudflare.com/?to=/:account/workers/containers) shows you helpful information about your Containers, including:

* Status and Health
* Metrics
* Logs

After launching your Worker, go to the Containers Dashboard by selecting**Workers & Pages** \> **Containers** in the dashboard sidebar.

## Next Steps

To do more:

* Modify the image by changing the Dockerfile and calling `wrangler deploy`
* Review our [examples](https://developers.cloudflare.com/containers/examples/) for more inspiration
* Review the [Frequently Asked Questions](https://developers.cloudflare.com/containers/faq/) for current platform behavior and limitations

```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 Interface
description: API reference for the Container interface and utility functions
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Container Interface

The [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://developers.cloudflare.com/containers/platform-details/durable-object-methods) and provides a higher-level API for common container behaviors.

Note

If you are using containers to run sandboxed code execution — for example, inside an AI agent — consider the [Sandbox SDK](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-5706)
* [  TypeScript ](#tab-panel-5707)

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);

  },

};


```

TypeScript

```

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);

  },

};


```

The `Container` class extends `DurableObject`, so all [Durable Object](https://developers.cloudflare.com/durable-objects) functionality is available.

## Properties

Configure 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://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 hooks

Override these methods to run Worker code when the container changes state. Refer to the [status hooks example](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-5702)
* [  TypeScript ](#tab-panel-5703)

JavaScript

```

import { Container } from "@cloudflare/containers";


export class MyContainer extends Container {

  defaultPort = 8080;


  async onStart() {

    await this.containerFetch("http://localhost/bootstrap", {

      method: "POST",

    });

  }

}


```

TypeScript

```

import { Container } from "@cloudflare/containers";


export class MyContainer extends Container {

  defaultPort = 8080;


    override async onStart() {

      await this.containerFetch("http://localhost/bootstrap", {

        method: "POST",

      });

    }


}


```

### `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-5700)
* [  TypeScript ](#tab-panel-5701)

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-5704)
* [  TypeScript ](#tab-panel-5705)

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).

Warning

If 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-5708)
* [  TypeScript ](#tab-panel-5709)

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();

  }

}


```

TypeScript

```

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();

    }


}


```

## 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-5710)
* [  TypeScript ](#tab-panel-5711)

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);

  }

}


```

TypeScript

```

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);

    }


}


```

`fetch` is the only method that supports WebSocket proxying. Refer to the [WebSocket example](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 overridden.

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-5720)
* [  TypeScript ](#tab-panel-5721)

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);

  }

}


```

TypeScript

```

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);

    }


}


```

## Start and stop

In 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-5716)
* [  TypeScript ](#tab-panel-5717)

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,

      },

    });

  },

};


```

TypeScript

```

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,

        },

      });

    },


};


```

Refer to the [env vars and secrets example](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-5712)
* [  TypeScript ](#tab-panel-5713)

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,

    });

  },

};


```

TypeScript

```

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,

      });

    },


};


```

Refer to the [cron example](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-5714)
* [  TypeScript ](#tab-panel-5715)

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);

  }

}


```

TypeScript

```

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);

    }


}


```

### `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-5718)
* [  TypeScript ](#tab-panel-5719)

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);

  }

}


```

TypeScript

```

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);

    }


}


```

### `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-5722)
* [  TypeScript ](#tab-panel-5723)

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);

  }

}


```

TypeScript

```

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);

    }


}


```

## 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-5724)
* [  TypeScript ](#tab-panel-5725)

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);

  }

}


```

TypeScript

```

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);

    }


}


```

### `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-5728)
* [  TypeScript ](#tab-panel-5729)

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",

      });

    }

  }

}


```

TypeScript

```

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",

        });

      }

    }


}


```

## 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://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-5732)
* [  TypeScript ](#tab-panel-5733)

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");

  }

}


```

TypeScript

```

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");

    }


}


```

## Outbound interception

Outbound 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-5736)
* [  TypeScript ](#tab-panel-5737)

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);

  },

};


```

TypeScript

```

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);

  },

};


```

For more information, refer to [Handle outbound traffic](https://developers.cloudflare.com/containers/platform-details/outbound-traffic/).

## Utility functions

These 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-5726)
* [  TypeScript ](#tab-panel-5727)

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-5730)
* [  TypeScript ](#tab-panel-5731)

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://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-5734)
* [  TypeScript ](#tab-panel-5735)

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: Examples
description: Code examples showing how to use Containers with Workers for stateless routing, cron jobs, WebSockets, and more.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Examples

Explore the following examples of Container functionality:

[Mount R2 buckets with FUSEMount R2 buckets as filesystems using FUSE in Containers](https://developers.cloudflare.com/containers/examples/r2-fuse-mount/)[Static Frontend, Container BackendA simple frontend app with a containerized backend](https://developers.cloudflare.com/containers/examples/container-backend/)[Cron ContainerRunning a container on a schedule using Cron Triggers](https://developers.cloudflare.com/containers/examples/cron/)[Using Durable Objects DirectlyVarious examples calling Containers directly from Durable Objects](https://developers.cloudflare.com/containers/examples/durable-object-interface/)[Env Vars and SecretsPass in environment variables and secrets to your container](https://developers.cloudflare.com/containers/examples/env-vars-and-secrets/)[Stateless InstancesRun multiple instances across Cloudflare's network](https://developers.cloudflare.com/containers/examples/stateless/)[Status HooksExecute Workers code in reaction to Container status changes](https://developers.cloudflare.com/containers/examples/status-hooks/)[Websocket to ContainerForwarding a Websocket request to a Container](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 Backend
description: A simple frontend app with a containerized backend
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Static Frontend, Container Backend

**Last reviewed:**  11 months ago 

A simple frontend app with a containerized backend

A 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://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-5738)
* [  wrangler.toml ](#tab-panel-5739)

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"

    }

  ]

}


```

TOML

```

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"


```

## Add a simple index.html file to serve

Create 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>


```

In 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://developers.cloudflare.com/workers/framework-guides/web-apps/) for more information.

## Define a Worker

Your 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")) {

      const containerInstance = await getRandom(env.BACKEND, INSTANCE_COUNT);

      return containerInstance.fetch(request);

    }


    return env.ASSETS.fetch(request);

  },

};


```

Note

This example uses `getRandom`, which randomly selects one of a fixed number of Container instances for each request.

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://developers.cloudflare.com/containers/platform-details/scaling-and-routing) for more details.

## Define a backend container

Your 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 main


import (

  "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))

}


```

```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 Container
description: Running a container on a schedule using Cron Triggers
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Cron Container

**Last reviewed:**  11 months ago 

Running a container on a schedule using Cron Triggers

To launch a container on a schedule, you can use a Workers [Cron Trigger](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-5740)
* [  wrangler.toml ](#tab-panel-5741)

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"

    }

  ]

}


```

TOML

```

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"


```

Then 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(),

      }

    })

  },

};


```

```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: Env Vars and Secrets
description: Pass in environment variables and secrets to your container
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Env Vars and Secrets

**Last reviewed:**  11 months ago 

Pass in environment variables and secrets to your container

Environment variables can be passed into a Container using the `envVars` field in the [Container](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://developers.cloudflare.com/workers/configuration/secrets/)or the [Secret Store](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://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 KV

In 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 data

First, 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://developers.cloudflare.com/workers/configuration/secrets/)and the [Secret Store documentation](https://developers.cloudflare.com/secrets-store/integrations/workers/). For KV setup, see the [Workers KV documentation](https://developers.cloudflare.com/kv/).

## Adding bindings

Next, we need to add bindings to access our secrets, KV values, and environment variables in Wrangler configuration.

* [  wrangler.jsonc ](#tab-panel-5742)
* [  wrangler.toml ](#tab-panel-5743)

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...

}


```

TOML

```

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>"


```

Note 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 class

Now, let's pass the env vars and secrets to our container using the `envVars` field in the `Container` class:

JavaScript

```

// https://developers.cloudflare.com/workers/runtime-apis/bindings/#importing-env-as-a-global

import { 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

  };

}


```

Every instance of this `Container` will now have these variables and secrets set as environment variables when it launches.

## Setting environment variables per-instance

But 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 ...

  },

};


```

## Reading KV values in containers

KV 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 data

JavaScript

```

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");

    }

  },

};


```

### Feature flags

JavaScript

```

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");

    }

  },

};


```

## Build-time environment variables

Finally, 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 FUSE
description: Mount R2 buckets as filesystems using FUSE in Containers
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Mount R2 buckets with FUSE

**Last reviewed:**  6 months ago 

Mount R2 buckets as filesystems using FUSE in Containers

FUSE (Filesystem in Userspace) allows you to mount [R2 buckets](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 considerations

Object 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://developers.cloudflare.com/agents/) or [sandboxes](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 buckets

To 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 dependencies

RUN apk add --no-cache \

    --repository http://dl-cdn.alpinelinux.org/alpine/v3.20/main \

    ca-certificates fuse curl bash


# Install tigrisfs

RUN 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 command

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\

    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.sh


EXPOSE 8080

CMD ["/startup.sh"]


```

The 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 container

Your Container needs [R2 credentials](https://developers.cloudflare.com/r2/api/tokens/) and configuration passed as environment variables. Store credentials as [Worker secrets](https://developers.cloudflare.com/workers/configuration/secrets/), then pass them through the `envVars` property:

* [  JavaScript ](#tab-panel-5744)
* [  TypeScript ](#tab-panel-5745)

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,

  };

}


```

src/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,

  };

}


```

The `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 keys

To 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 providers

Other 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 prefixes

To 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


```

Your 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-only

To 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


```

This is useful for shared assets or configuration files where you want to ensure applications only read data.

## Related resources

* [Container environment variables](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 Instances
description: Run multiple instances across Cloudflare's network
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Stateless Instances

**Last reviewed:**  11 months ago 

Run multiple instances across Cloudflare's network

To 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> {

    const containerInstance = await getRandom(env.BACKEND, INSTANCE_COUNT);

    return containerInstance.fetch(request);

  },

};


```

Note

This example uses `getRandom`, which randomly selects one of a fixed number of Container instances for each request.

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://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 Hooks
description: Execute Workers code in reaction to Container status changes
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Status Hooks

**Last reviewed:**  11 months ago 

Execute Workers code in reaction to Container status changes

When 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 class lifecycle hooks](https://developers.cloudflare.com/containers/container-class/#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);

  }

}


```

```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 Container
description: Forwarding a Websocket request to a Container
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Websocket to Container

**Last reviewed:**  11 months ago 

Forwarding a Websocket request to a Container

WebSocket 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);

  },

};


```

View 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 Container
description: Understand how a Container is deployed, started, routed, and shut down across Cloudflare's network.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Lifecycle of a Container

## Deployment

After you deploy an application with a Container, your image is uploaded to[Cloudflare's Registry](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 shut down any running instances during a rollout. Refer to [rollouts](https://developers.cloudflare.com/containers/platform-details/rollouts/) for more details.

## Lifecycle of a Request

### Client to Worker

Recall that Containers are backed by [Durable Objects](https://developers.cloudflare.com/durable-objects/) and [Workers](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://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 Object

From the Worker, a request passes through a Durable Object instance (the [Container class](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 Container

When a Durable Object instance requests to start a new container instance, the **nearest location with a pre-fetched image** is selected.

Note

Durable Objects and their associated Container instances are not guaranteed to run in the same location.

Container placement is optimized for request routing and startup speed, so a Container may start in a different location than its 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 starts

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 in the 1-3 second range, but this is dependent on image size and code execution time, among other factors.

### Requests to running Containers

When 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 runtime

Each 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://developers.cloudflare.com/containers/platform-details/limits/).

[Logging](https://developers.cloudflare.com/containers/faq/#how-do-container-logs-work), metrics collection, and[networking](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 shutdown

If you do not set [sleepAfter](https://developers.cloudflare.com/containers/container-class/#sleepafter)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 shut down a container instance by calling [stop()](https://developers.cloudflare.com/containers/container-class/#stop) or [destroy()](https://developers.cloudflare.com/containers/container-class/#destroy) on it.

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.

### Lifecycle hooks

The [Container class](https://developers.cloudflare.com/containers/container-class/) provides hooks that run Worker code when the container changes state:

* [onStart()](https://developers.cloudflare.com/containers/container-class/#onstart) — Runs after the container has started.
* [onStop()](https://developers.cloudflare.com/containers/container-class/#onstop) — Runs after the container process exits. Receives the exit code and reason for the stop.
* [onActivityExpired()](https://developers.cloudflare.com/containers/container-class/#onactivityexpired) — Runs when the [sleepAfter](https://developers.cloudflare.com/containers/container-class/#sleepafter) timer expires with no incoming requests. The default implementation calls `stop()` to shut down the container. You can use this to only stop the container on certain conditions.
* [onError()](https://developers.cloudflare.com/containers/container-class/#onerror) — Runs when the container exits with an error.

Refer to the [status hooks example](https://developers.cloudflare.com/containers/examples/status-hooks/) for a full implementation.

#### Persistent disk

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.

Snapshots are coming soon, which allow the user to quickly persist and restore the disk from an entire container or a directory.

You can also use [FUSE](https://developers.cloudflare.com/containers/examples/r2-fuse-mount/) to persist disk to R2 or other object storage backends. Though you should not expect native SSD-like performance while using FUSE.

## 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 Container
description: Understand how a Container is deployed, started, routed, and shut down across Cloudflare's network.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Lifecycle of a Container

## Deployment

After you deploy an application with a Container, your image is uploaded to[Cloudflare's Registry](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 shut down any running instances during a rollout. Refer to [rollouts](https://developers.cloudflare.com/containers/platform-details/rollouts/) for more details.

## Lifecycle of a Request

### Client to Worker

Recall that Containers are backed by [Durable Objects](https://developers.cloudflare.com/durable-objects/) and [Workers](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://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 Object

From the Worker, a request passes through a Durable Object instance (the [Container class](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 Container

When a Durable Object instance requests to start a new container instance, the **nearest location with a pre-fetched image** is selected.

Note

Durable Objects and their associated Container instances are not guaranteed to run in the same location.

Container placement is optimized for request routing and startup speed, so a Container may start in a different location than its 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 starts

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 in the 1-3 second range, but this is dependent on image size and code execution time, among other factors.

### Requests to running Containers

When 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 runtime

Each 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://developers.cloudflare.com/containers/platform-details/limits/).

[Logging](https://developers.cloudflare.com/containers/faq/#how-do-container-logs-work), metrics collection, and[networking](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 shutdown

If you do not set [sleepAfter](https://developers.cloudflare.com/containers/container-class/#sleepafter)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 shut down a container instance by calling [stop()](https://developers.cloudflare.com/containers/container-class/#stop) or [destroy()](https://developers.cloudflare.com/containers/container-class/#destroy) on it.

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.

### Lifecycle hooks

The [Container class](https://developers.cloudflare.com/containers/container-class/) provides hooks that run Worker code when the container changes state:

* [onStart()](https://developers.cloudflare.com/containers/container-class/#onstart) — Runs after the container has started.
* [onStop()](https://developers.cloudflare.com/containers/container-class/#onstop) — Runs after the container process exits. Receives the exit code and reason for the stop.
* [onActivityExpired()](https://developers.cloudflare.com/containers/container-class/#onactivityexpired) — Runs when the [sleepAfter](https://developers.cloudflare.com/containers/container-class/#sleepafter) timer expires with no incoming requests. The default implementation calls `stop()` to shut down the container. You can use this to only stop the container on certain conditions.
* [onError()](https://developers.cloudflare.com/containers/container-class/#onerror) — Runs when the container exits with an error.

Refer to the [status hooks example](https://developers.cloudflare.com/containers/examples/status-hooks/) for a full implementation.

#### Persistent disk

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.

Snapshots are coming soon, which allow the user to quickly persist and restore the disk from an entire container or a directory.

You can also use [FUSE](https://developers.cloudflare.com/containers/examples/r2-fuse-mount/) to persist disk to R2 or other object storage backends. Though you should not expect native SSD-like performance while using FUSE.

## 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 Container
description: Access and manage containers associated with a Durable Object, including start, stop, and interaction methods.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/durable-objects/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Durable Object Container

## Description

When using a [Container-enabled Durable Object](https://developers.cloudflare.com/containers), you can access the Durable Object's associated container via the `container` object which is on the `ctx` property. This allows you to start, stop, and interact with the container.

Note

It is likely preferable to use the official `Container` class, which provides helper methods and a more idiomatic API for working with containers on top of Durable Objects.

* [  JavaScript ](#tab-panel-6088)
* [  TypeScript ](#tab-panel-6089)

index.js

```

export class MyDurableObject extends DurableObject {

  constructor(ctx, env) {

    super(ctx, env);


    // boot the container when starting the DO

    this.ctx.blockConcurrencyWhile(async () => {

      this.ctx.container.start();

    });

  }

}


```

index.ts

```

export class MyDurableObject extends DurableObject {

  constructor(ctx: DurableObjectState, env: Env) {

    super(ctx, env);


      // boot the container when starting the DO

      this.ctx.blockConcurrencyWhile(async () => {

        this.ctx.container.start();

    });

    }


}


```

## Attributes

### `running`

`running` returns `true` if the container is currently running. It does not ensure that the container has fully started and ready to accept requests.

JavaScript

```

  this.ctx.container.running;


```

## Methods

### `start`

`start` boots a container. This method does not block until the container is fully started. You may want to confirm the container is ready to accept requests before using it.

JavaScript

```

this.ctx.container.start({

  env: {

    FOO: "bar",

  },

  enableInternet: false,

  entrypoint: ["node", "server.js"],

});


```

#### Parameters

* `options` (optional): An object with the following properties:  
   * `env`: An object containing environment variables to pass to the container. This is useful for passing configuration values or secrets to the container.  
   * `entrypoint`: An array of strings representing the command to run in the container.  
   * `enableInternet`: A boolean indicating whether to enable internet access for the container.

#### Return values

* None.

### `destroy`

`destroy` stops the container and optionally returns a custom error message to the `monitor()` error callback.

JavaScript

```

this.ctx.container.destroy("Manually Destroyed");


```

#### Parameters

* `error` (optional): A string that will be sent to the error handler of the `monitor` method. This is useful for logging or debugging purposes.

#### Return values

* A promise that returns once the container is destroyed.

### `signal`

`signal` sends an IPC signal to the container, such as SIGKILL or SIGTERM. This is useful for stopping the container gracefully or forcefully.

JavaScript

```

const SIGTERM = 15;

this.ctx.container.signal(SIGTERM);


```

#### Parameters

* `signal`: a number representing the signal to send to the container. This is typically a POSIX signal number, such as SIGTERM (15) or SIGKILL (9).

#### Return values

* None.

### `getTcpPort`

`getTcpPort` returns a TCP port from the container. This can be used to communicate with the container over TCP and HTTP.

JavaScript

```

const port = this.ctx.container.getTcpPort(8080);

const res = await port.fetch("http://container/set-state", {

  body: initialState,

  method: "POST",

});


```

JavaScript

```

const conn = this.ctx.container.getTcpPort(8080).connect("10.0.0.1:8080");

await conn.opened;


try {

  if (request.body) {

    await request.body.pipeTo(conn.writable);

  }

  return new Response(conn.readable);

} catch (err) {

  console.error("Request body piping failed:", err);

  return new Response("Failed to proxy request body", { status: 502 });

}


```

#### Parameters

* `port` (number): a TCP port number to use for communication with the container.

#### Return values

* `TcpPort`: a `TcpPort` object representing the TCP port. This object can be used to send requests to the container over TCP and HTTP.

### `monitor`

`monitor` returns a promise that resolves when a container exits and errors if a container errors. This is useful for setting up callbacks to handle container status changes in your Workers code.

JavaScript

```

class MyContainer extends DurableObject {

  constructor(ctx, env) {

    super(ctx, env);

    function onContainerExit() {

      console.log("Container exited");

    }


    // the "err" value can be customized by the destroy() method

    async function onContainerError(err) {

      console.log("Container errored", err);

    }


    this.ctx.container.start();

    this.ctx.container.monitor().then(onContainerExit).catch(onContainerError);

  }

}


```

#### Parameters

* None

#### Return values

* A promise that resolves when the container exits.

### `interceptOutboundHttp`

`interceptOutboundHttp` routes outbound HTTP requests matching a hostname, hostname glob, IP address, IP:port, or CIDR range through a `WorkerEntrypoint`. Can be called before or after starting the container. Open connections pick up the new handler without being dropped.

JavaScript

```

const worker = this.ctx.exports.MyWorker({ props: { message: "hello" } });


// Match a specific hostname

this.ctx.container.interceptOutboundHttp("api.example.com", worker);


// Match a hostname glob pattern

this.ctx.container.interceptOutboundHttp("*.example.com", worker);


// Match an IP:port

await this.ctx.container.interceptOutboundHttp("15.0.0.1:80", worker);


// Match a CIDR range (IPv4 and IPv6)

await this.ctx.container.interceptOutboundHttp("123.123.123.123/23", worker);


```

#### Parameters

* `target` (string): A hostname, hostname glob (for example, `*.example.com`), IP address, IP:port, or CIDR range to match.
* `worker` (WorkerEntrypoint): A `WorkerEntrypoint` instance to handle matching requests.

#### Return values

* None.

### `interceptAllOutboundHttp`

`interceptAllOutboundHttp` routes all outbound HTTP requests from the container through a `WorkerEntrypoint`, regardless of destination.

JavaScript

```

await this.ctx.container.interceptAllOutboundHttp(worker);


```

#### Parameters

* `worker` (WorkerEntrypoint): A `WorkerEntrypoint` instance to handle all outbound HTTP requests.

#### Return values

* A promise that resolves once the intercept rule is installed.

### `interceptOutboundHttps`

`interceptOutboundHttps` routes outbound HTTPS requests matching a hostname or hostname glob through a `WorkerEntrypoint`. Works the same way as `interceptOutboundHttp` but for HTTPS traffic. The container must trust the CA certificate at `/etc/cloudflare/certs/cloudflare-containers-ca.crt` for HTTPS interception to work.

Supports glob patterns where `*` matches any sequence of characters.

JavaScript

```

const worker = this.ctx.exports.MyWorker({ props: {} });


// Match a specific hostname

this.ctx.container.interceptOutboundHttps("api.example.com", worker);


// Match a hostname glob pattern

this.ctx.container.interceptOutboundHttps("*.example.com", worker);


// Intercept all HTTPS traffic

this.ctx.container.interceptOutboundHttps("*", worker);


```

#### Parameters

* `target` (string): A hostname or hostname glob pattern to match. Use `*` to intercept all HTTPS traffic.
* `worker` (WorkerEntrypoint): A `WorkerEntrypoint` instance to handle matching requests.

#### Return values

* None.

## Related resources

* [Containers](https://developers.cloudflare.com/containers)
* [Get Started With Containers](https://developers.cloudflare.com/containers/get-started)

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/durable-objects/","name":"Durable Objects"}},{"@type":"ListItem","position":3,"item":{"@id":"/durable-objects/api/","name":"Workers Binding API"}},{"@type":"ListItem","position":4,"item":{"@id":"/durable-objects/api/container/","name":"Durable Object Container"}}]}
```

---

---
title: Environment Variables
description: Runtime and user-defined environment variables available inside Container instances.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Environment Variables

## Runtime environment variables

The 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 variables

You 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://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 Management
description: Learn how to use Cloudflare Registry, Docker Hub, and Amazon ECR images with Containers.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Image Management

## Push images during `wrangler deploy`

When running `wrangler deploy`, if you set the `image` attribute in your [Wrangler configuration](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://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-5750)
* [  wrangler.toml ](#tab-panel-5751)

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.

Note

Docker 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 images

Containers 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/).

Note

Cloudflare 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 images

To 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-5752)
* [  wrangler.toml ](#tab-panel-5753)

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.

Note

Official Docker Hub images use the `library` namespace. For example, use `docker.io/library/<IMAGE>:<TAG>` instead of `docker.io/<IMAGE>:<TAG>`.

### Configure private registry credentials

To use a private image from Docker Hub or Amazon ECR, run [wrangler containers registries configure](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://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 images

Configure 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 access

To 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 images

Configure 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 key

Public 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"

      ]

    }

  ]

}


```

After you create the IAM user, use its credentials to [configure the registry in Wrangler](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-5754)
* [  wrangler.toml ](#tab-panel-5755)

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 registries

If 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-5756)
* [  wrangler.toml ](#tab-panel-5757)

JSONC

```

{

  "containers": [

    {

      "image": "registry.cloudflare.com/<YOUR_ACCOUNT_ID>/<IMAGE>:<TAG>"

    }

  ]

}


```

TOML

```

[[containers]]

image = "registry.cloudflare.com/<YOUR_ACCOUNT_ID>/<IMAGE>:<TAG>"


```

Note

With `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 CI

To 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 limits

Images are limited in size by available disk of the configured [instance type](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 Types
description: Available Container instance types and account-level limits for memory, vCPU, disk, and image storage.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Limits and Instance Types

## Instance Types

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](#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://developers.cloudflare.com/workers/wrangler/configuration/#containers) in your Worker's Wrangler configuration file.

Note

The `dev` and `standard` instance types are preserved for backward compatibility and are aliases for `lite` and `standard-1`, respectively.

### Custom Instance Types

In 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://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`.

If you need larger instance sizes or higher account-level limits, contact your account team, file a support ticket, or fill out [this form ↗](https://forms.gle/CscdaEGuw5Hb6H2s7).

## Limits

The following limits currently apply:

| 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)                  |

## Footnotes

1. Delete container images with `wrangler containers delete` to free up space. If you delete a container image and then [roll back](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 traffic
description: Intercept and handle outbound HTTP from containers using Workers.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Handle outbound traffic

Outbound 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://developers.cloudflare.com/containers/platform-details/workers-connections/) like KV, R2, and Durable Objects

## Block outbound traffic

Use `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 host

You can filter outbound traffic with the `allowedHosts` and `deniedHosts` properties on the Container class.

Note

Export `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"];

}


```

## Define outbound handlers

Outbound 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);

};


```

Note

HTTP 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);

  },

};


```

Calls 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 credentials

Because 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);

  },

};


```

This 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);

  },

};


```

## HTTPS traffic

By 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 certificate

For 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-5758)
* [ Alpine ](#tab-panel-5759)
* [ Fedora/RHEL ](#tab-panel-5760)
* [ Arch ](#tab-panel-5761)

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(" && "),

  ];

}


```

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(" && "),

  ];

}


```

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 /etc/pki/ca-trust/source/anchors/cloudflare-containers-ca.crt",

      "update-ca-trust",

      "exec node server.js",

    ].join(" && "),

  ];

}


```

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 /etc/ca-certificates/trust-source/anchors/cloudflare-containers-ca.crt",

      "trust extract-compat",

      "exec node server.js",

    ].join(" && "),

  ];

}


```

Replace `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`.

Note

HTTP 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 traffic

Outbound 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 runtime

Use `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);

  },

};


```

Apply 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");

}


```

## Handler precedence

Requests 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 API

To 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 DurableObject

this.ctx.container.start({ enableInternet: false });

const worker = this.ctx.exports.MyOutboundWorker({ props: {} });

await this.ctx.container.interceptAllOutboundHttp(worker);


```

You 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 range

await this.ctx.container.interceptOutboundHttp("203.0.113.0/24", worker);

// Intercept by hostname

this.ctx.container.interceptOutboundHttp("foo.com", worker);


// Update the handler while the container is running

const updated = this.ctx.exports.MyOutboundWorker({

  props: { phase: "post-install" },

});

await this.ctx.container.interceptOutboundHttp("203.0.113.0/24", updated);


```

For HTTPS, `interceptOutboundHttps` works the same way as `interceptOutboundHttp`.

JavaScript

```

// Intercept a specific hostname

this.ctx.container.interceptOutboundHttps("foo.com", worker);


// Intercept all traffic

this.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://developers.cloudflare.com/containers/platform-details/workers-connections/) — Access KV, R2, Durable Objects, and other bindings from a container
* [Control outbound traffic (Sandboxes)](https://developers.cloudflare.com/sandbox/guides/outbound-traffic/) — Sandbox SDK API for outbound handlers
* [Environment variables and secrets](https://developers.cloudflare.com/containers/platform-details/environment-variables/) — Configure secrets and environment variables
* [Durable Object interface](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: Placement
description: Control where your containers run with regional and jurisdictional constraints.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Placement

By default, containers run in the location nearest to the incoming request with a pre-fetched image. Use placement constraints to restrict where your containers run for data residency, compliance, or latency requirements.

## Regional constraints

Use the `regions` constraint to limit container placement to specific geographic areas:

| Region | Description           | Notes            |
| ------ | --------------------- | ---------------- |
| ENAM   | Eastern North America |                  |
| WNAM   | Western North America |                  |
| EEUR   | Eastern Europe        |                  |
| WEUR   | Western Europe        |                  |
| APAC   | Asia Pacific          |                  |
| SAM    | South America         |                  |
| ME     | Middle East           | Limited capacity |
| OC     | Oceania               | Limited capacity |
| AFR    | Africa                | Limited capacity |

Limited capacity regions (ME, OC, AFR) cannot be used exclusively. Include at least one other region, or contact support for dedicated access.

## Jurisdictional constraints

Use the `jurisdiction` constraint to restrict containers to compliance boundaries:

| Jurisdiction | Regions    | Use case          |
| ------------ | ---------- | ----------------- |
| eu           | EEUR, WEUR | EU data residency |
| fedramp      | ENAM, WNAM | FedRAMP regions   |

When you specify both `jurisdiction` and `regions`, the regions must be valid for that jurisdiction. For example, specifying `jurisdiction: "eu"` with `regions: ["ENAM"]` is invalid.

## Configure placement

Set placement constraints in your Wrangler configuration:

* [  wrangler.jsonc ](#tab-panel-5762)
* [  wrangler.toml ](#tab-panel-5763)

JSONC

```

{

  "$schema": "./node_modules/wrangler/config-schema.json",

  "containers": [

    {

      "name": "my-container",

      "image": "docker.io/my-org/my-image:latest",

      "constraints": {

        "regions": [

          "ENAM",

          "WNAM"

        ],

        "jurisdiction": "fedramp"

      }

    }

  ]

}


```

TOML

```

[[containers]]

name = "my-container"

image = "docker.io/my-org/my-image:latest"


[containers.constraints]

regions = ["ENAM", "WNAM"]

jurisdiction = "fedramp"


```

Refer to [Lifecycle of a Container](https://developers.cloudflare.com/containers/platform-details/architecture/) for more details on how placement affects container startup and routing.

```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/placement/","name":"Placement"}}]}
```

---

---
title: Rollouts
description: Configure rolling deployments for Containers, including step percentages and grace periods for active instances.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Rollouts

## How rollouts work

When 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://developers.cloudflare.com/workers/wrangler/configuration/#containers) property.

When deploying a change, you can also configure a [rollout\_active\_grace\_period](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 a `SIGTERM` signal 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.

Because Worker code updates immediately while container instances roll out gradually, keep changes backwards compatible across both versions until the rollout completes.

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-5764)
* [  wrangler.toml ](#tab-panel-5765)

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"],

    },

  ],

}


```

TOML

```

[[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" ]


```

## Immediate rollouts

If 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 Routing
description: Scale Container instances using explicit IDs or the getRandom helper for stateless load balancing.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Scaling and Routing

## Scale container instances with explicit IDs

Note

This section uses helpers from the [Container class](https://developers.cloudflare.com/containers/container-class/).

Today, Containers are 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 instances

const containerOne = getContainer(

  env.MY_CONTAINER,

  idOne,

).startAndWaitForPorts();


const containerTwo = getContainer(

  env.MY_CONTAINER,

  idTwo,

).startAndWaitForPorts();


```

Each 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.

### Use the `getRandom` helper function

If you want to run multiple instances of a container and route requests between them, use the`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> {

    const containerInstance = await getRandom(env.BACKEND, INSTANCE_COUNT);

    return containerInstance.fetch(request);

  },

};


```

Use `getRandom` to route to multiple stateless container instances. It randomly selects one of N instances for each request, which means:

* 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 Bindings
description: Access KV, R2, Durable Objects, and other bindings from a container.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Connect to Workers and Bindings

Containers can access [Workers bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/) — KV, R2, D1, Durable Objects, and others — through [outbound handlers](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 handlers

Define 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 });

  },

};


```

The 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.

Note

You can use `ctx.containerId` to apply different rules per container instance — for example, to look up per-instance configuration from KV.

## Access Durable Object state

The `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://developers.cloudflare.com/containers/platform-details/outbound-traffic/) — Block, allow, and intercept all outbound HTTP from a container
* [Environment variables and secrets](https://developers.cloudflare.com/containers/platform-details/environment-variables/) — Configure secrets and environment variables
* [Durable Object interface](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 Development
description: Learn how to run Container-enabled Workers locally with `wrangler dev` and `vite dev`.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Local Development

You can run both your container and your Worker locally by simply running [npx wrangler dev](https://developers.cloudflare.com/workers/wrangler/commands/general/#dev) (or `vite dev` for Vite projects using the [Cloudflare Vite plugin](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://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.

Note

With `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.

Note

If 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 code

When 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 Ports

In 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 VPN

If 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  
```  
Note  
The 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: Containers
description: Wrangler commands for interacting with Cloudflare's Container Platform.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/workers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Containers

Interact with [Containers](https://developers.cloudflare.com/containers/) using Wrangler.

### `build`

Build a Container image from a Dockerfile.

```

wrangler containers build [PATH] [OPTIONS]


```

* `PATH` ` string ` optional  
   * Path for the directory containing the Dockerfile to build.
* `-t, --tag` ` string ` required  
   * Name and optionally a tag (format: "name:tag").
* `--path-to-docker` ` string ` optional  
   * Path to your docker binary if it's not on `$PATH`.  
   * Default: "docker"
* `-p, --push` ` boolean ` optional  
   * Push the built image to Cloudflare's managed registry.  
   * Default: false

### `delete`

Delete a Container (application).

```

wrangler containers delete <CONTAINER_ID> [OPTIONS]


```

* `CONTAINER_ID` ` string ` required  
   * The ID of the Container to delete.

### `images`

Perform operations on images in your containers registry.

#### `images list`

List images in your containers registry.

```

wrangler containers images list [OPTIONS]


```

* `--filter` ` string ` optional  
   * Regex to filter results.
* `--json` ` boolean ` optional  
   * Return output as clean JSON.  
   * Default: false

#### `images delete`

Remove an image from your containers registry.

```

wrangler containers images delete [IMAGE] [OPTIONS]


```

* `IMAGE` ` string ` required  
   * Image to delete of the form `IMAGE:TAG`

### `registries`

Configure and view registries available to your container.[Read more](https://developers.cloudflare.com/containers/platform-details/image-management/#using-amazon-ecr-container-images) about our currently supported external registries.

#### `registries list`

List registries your containers are able to use.

```

wrangler containers registries list [OPTIONS]


```

* `--json` ` boolean ` optional  
   * Return output as clean JSON.  
   * Default: false

#### `registries configure`

Configure a new registry for your account.

```

wrangler containers registries configure [DOMAIN] [OPTIONS]


```

* `DOMAIN` ` string ` required  
   * Domain to configure for the registry.
* `--public-credential` ` string ` required  
   * The public part of the registry credentials, e.g. `AWS_ACCESS_KEY_ID` for ECR
* `--secret-store-id` ` string ` optional  
   * The ID of the secret store to use to store the registry credentials
* `--secret-name` ` string ` optional  
   * The name Wrangler should store the registry credentials under

When run interactively, wrangler will prompt you for your secret and store it in Secrets Store. To run non-interactively, you can send your secret value to wrangler through stdin to have the secret created for you.

#### `registries delete`

Remove a registry configuration from your account.

```

wrangler containers registries delete [DOMAIN] [OPTIONS]


```

* `DOMAIN` ` string ` required  
   * domain of the registry to delete

#### `registries credentials`

Generate temporary credentials to push or pull images from the Cloudflare managed registry (`registry.cloudflare.com`).

```

wrangler containers registries credentials [OPTIONS]


```

* `--push` ` boolean ` optional  
   * Generate credentials with push permission.
* `--pull` ` boolean ` optional  
   * Generate credentials with pull permission.
* `--expiration-minutes` ` number ` optional  
   * How long the credentials should be valid for (in minutes).  
   * Default: 15

At least one of `--push` or `--pull` must be specified.

### `info`

Get information about a specific Container, including top-level details and a list of instances.

```

wrangler containers info <CONTAINER_ID> [OPTIONS]


```

* `CONTAINER_ID` ` string ` required  
   * The ID of the Container to get information about.

### `instances`

List all Container instances for a given application. Displays instance ID, name, state, location, version, and creation time.

In interactive mode, results are paginated. Press `Enter` to load the next page or `Esc`/`q` to stop. In non-interactive environments (for example, when piping output or running in CI), all pages are fetched automatically.

Use the `--json` flag to return output as a flat JSON array. Each element contains the fields `id`, `name`, `state`, `location`, `version`, and `created`. This is also the default output format in non-interactive environments.

```

wrangler containers instances <APPLICATION_ID> [OPTIONS]


```

* `APPLICATION_ID` ` string ` required  
   * The UUID of the application to list instances for. Use `wrangler containers list` to find application IDs.
* `--per-page` ` number ` optional  
   * Number of instances per page.  
   * Default: 25
* `--json` ` boolean ` optional  
   * Return output as clean JSON.  
   * Default: false

For example, to list instances for an application:

Terminal window

```

wrangler containers instances 12345678-abcd-1234-abcd-123456789abc


```

```

INSTANCE                              NAME        STATE          LOCATION  VERSION  CREATED

a1b2c3d4-e5f6-7890-abcd-ef1234567890  worker-12   running        sfo06     3        2025-06-01T12:00:00Z

b2c3d4e5-f6a7-8901-bcde-f12345678901  worker-47   provisioning   iad01     2        2025-06-01T13:00:00Z


```

To get the same data as JSON:

Terminal window

```

wrangler containers instances 12345678-abcd-1234-abcd-123456789abc --json


```

```

[

  {

    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",

    "name": "worker-12",

    "state": "running",

    "location": "sfo06",

    "version": 3,

    "created": "2025-06-01T12:00:00Z"

  }

]


```

### `list`

List the Containers in your account.

```

wrangler containers list [OPTIONS]


```

### `push`

Push a tagged image to a Cloudflare managed registry, which is automatically integrated with your account.

```

wrangler containers push [TAG] [OPTIONS]


```

* `TAG` ` string ` required  
   * The name and tag of the container image to push.
* `--path-to-docker` ` string ` optional  
   * Path to your docker binary if it's not on `$PATH`.  
   * Default: "docker"

### `ssh`

Connect to a running Container instance using SSH. Refer to [SSH](https://developers.cloudflare.com/containers/ssh/) for configuration details.

```

wrangler containers ssh <INSTANCE_ID>


```

You can also specify a command to run, instead of the default shell. For example:

```

wrangler containers ssh <INSTANCE_ID> -- ls -al


```

* `INSTANCE_ID` ` string ` required  
   * The ID of the Container instance to SSH into.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/workers/","name":"Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/workers/wrangler/","name":"Wrangler"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/wrangler/commands/","name":"Commands"}},{"@type":"ListItem","position":5,"item":{"@id":"/workers/wrangler/commands/containers/","name":"Containers"}}]}
```

---

---
title: Frequently Asked Questions
description: Answers to common questions about Containers, including logging, scaling, cold starts, disk persistence, and rollouts.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Frequently 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-5746)
* [  wrangler.toml ](#tab-panel-5747)

JSONC

```

{

  "observability": {

    "enabled": true

  }

}


```

TOML

```

[observability]

enabled = true


```

Logs are subject to the same [limits as Worker logs](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://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://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, Argentina. 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://developers.cloudflare.com/containers/platform-details/rollouts/) for details.

## How does scaling work?

Containers scale by creating or addressing specific instances. For stateless routing across a fixed number of interchangeable instances, use the `getRandom` helper.

Refer to [scaling and routing](https://developers.cloudflare.com/containers/platform-details/scaling-and-routing/) for details.

### Is built-in autoscaling for stateless applications available?

Not today, though Cloudflare plans to add built-in autoscaling in a future release.

Until then, use `getRandom` for simple stateless routing and specific instance IDs when you need explicit control over container lifecycle.

## 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 in the 1-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://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.

Snapshots are coming soon, which allow the user to quickly persist and restore the disk from an entire container or a directory.

You can also use [FUSE](https://developers.cloudflare.com/containers/examples/r2-fuse-mount/) to persist disk to R2 or other object storage backends. Though you should not expect native SSD-like performance while using FUSE.

## 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 shut down?

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://developers.cloudflare.com/workers/configuration/secrets/) or the [Secrets Store](https://developers.cloudflare.com/secrets-store/integrations/workers/)to define secrets for your Workers.

For implementation details, refer to [Environment variables and secrets](https://developers.cloudflare.com/containers/examples/env-vars-and-secrets/).

## 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 app

ENTRYPOINT ["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/sh

set -eu


# Wait for dockerd to be ready

until docker version >/dev/null 2>&1; do

  sleep 0.2

done


exec /path/to/your-app


```

Working with disabled iptables

Cloudflare 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?

Refer to [Handle outbound traffic](https://developers.cloudflare.com/containers/platform-details/outbound-traffic/) for how to control outbound traffic and internet access.

```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: SSH
description: Connect to running container instances with SSH.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# SSH

Anyone with write access to a Container can SSH into it with Wrangler as long as a matching public key is listed in `authorized_keys`.

SSH does not expose a publicly accessible port on the Container. The only way to connect is through Wrangler with [wrangler containers ssh](https://developers.cloudflare.com/workers/wrangler/commands/containers/#containers-ssh), which authenticates against your Cloudflare account.

## Configure SSH

SSH can be configured in your [Container's configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#containers) with the `ssh` and `authorized_keys` properties. Only the `ssh-ed25519` key type is supported.

The `ssh.enabled` property only controls whether you can SSH into a Container through Wrangler. It defaults to `true`. Set it to `false` to disable SSH access completely.

## Connect with Wrangler

To SSH into a Container with Wrangler, add an `ssh-ed25519` public key to `authorized_keys` in your Container configuration. The following example shows a basic configuration:

* [  wrangler.jsonc ](#tab-panel-5766)
* [  wrangler.toml ](#tab-panel-5767)

JSONC

```

{

  "containers": [

    {

      // other options here...

      "authorized_keys": [

        {

          "name": "<NAME>",

          "public_key": "<YOUR_PUBLIC_KEY_HERE>"

        }

      ]

    }

  ]

}


```

TOML

```

[[containers]]

[[containers.authorized_keys]]

name = "<NAME>"

public_key = "<YOUR_PUBLIC_KEY_HERE>"


```

For more information on configuring SSH, refer to [SSH configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#ssh).

Find the instance ID for your Container by running [wrangler containers instances](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 visibility

Without the [containers\_pid\_namespace](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://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: Pricing
description: Billing rates for Containers vCPU, memory, disk, and network egress, including included usage on the Workers Paid plan.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/containers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Pricing

## vCPU, Memory and Disk

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](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 Types

When you deploy a container, you specify an [instance type](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 Egress

Egress 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 Pricing

When you use Containers, incoming requests to your containers are handled by your [Worker](https://developers.cloudflare.com/workers/platform/pricing/), and each container has its own[Durable Object](https://developers.cloudflare.com/durable-objects/platform/pricing/). You are billed for your usage of both Workers and Durable Objects.

## Logs and Observability

Containers are integrated with the [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/) platform, and billed at the same rate. Refer to [Workers Logs pricing](https://developers.cloudflare.com/workers/observability/logs/workers-logs/#pricing) for details.

When you [enable observability for your Worker](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"}}]}
```
