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

[Skip to content](#main-content)

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

# Containers

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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:

```js
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);
	},
};
```

```jsonc
{
	"name": "container-starter",
	"main": "src/index.js",
	// Set this to today's date
	"compatibility_date": "2026-09-05",
	"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-09-05"

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

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

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

Deploy a Container

[Examples](https://developers.cloudflare.com/containers/examples/)

Stateless and stateful routing, regional placement, Workflow and Queue integrations, AI-generated code execution, and short-lived workloads.

See Examples

[Local development](https://developers.cloudflare.com/containers/guides/local-dev/)

Run your Worker and container together with `wrangler dev` or `vite dev` before you deploy.

Develop locally

[Deploy](https://developers.cloudflare.com/containers/guides/deploy/)

Ship from your machine or Workers Builds, and confirm the deploy.

Deploy Containers

---

## More resources

### [Rollouts](https://developers.cloudflare.com/containers/configuration/rollouts/)

How container instances update after you deploy.

### [Image management](https://developers.cloudflare.com/containers/guides/image-management/)

Build, push, and pull images for Containers.

### [Lifecycle of a Container](https://developers.cloudflare.com/containers/concepts/architecture/)

How a container is scheduled, started, routed, and shut down.

### [Limits](https://developers.cloudflare.com/containers/platform/limits/)

Instance counts, image size, and other platform limits.

### [Wrangler](https://developers.cloudflare.com/workers/wrangler/commands/containers/#containers)

CLI commands for images and containers.

### [Durable Object Container API](https://developers.cloudflare.com/durable-objects/api/container/)

Start, stop, and talk to the container process from a Durable Object.

### [SSH](https://developers.cloudflare.com/containers/guides/ssh/)

Connect to running container instances with SSH through Wrangler.

### [Containers Discord](https://discord.cloudflare.com)

Ask questions, show what you are building, and talk with other Containers developers.

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"WebPage","@id":"https://developers.cloudflare.com/containers/#page","headline":"Overview · Cloudflare Containers docs","description":"Run serverless containers alongside Workers to handle resource-intensive workloads, custom runtimes, and existing container images on Cloudflare.","url":"https://developers.cloudflare.com/containers/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Deploy your first Container on Cloudflare by building an image, configuring a Worker, and routing requests to container instances.
title: Get started
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Get started

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/get-started/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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:

npmyarnpnpm

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

npmyarnpnpm

```
npx wrangler deploy
```

```
yarn wrangler deploy
```

```
pnpm wrangler deploy
```

On deploy, Wrangler uploads your Worker, builds and pushes the container image with Docker, and updates container instances on Cloudflare's network. The first build and push usually take the longest. Later deploys [reuse cached image layers ↗](https://docs.docker.com/build/cache/).

Note

After you deploy your Worker for the first time, wait several minutes before you expect container requests to succeed. The Worker URL may respond while Cloudflare is still provisioning containers. During that time, calls into the container can error.

### Check deployment status

After deploying, list containers in your account and their status:

npmyarnpnpm

```
npx wrangler containers list
```

```
yarn wrangler containers list
```

```
pnpm wrangler containers list
```

List images in the Cloudflare Registry:

npmyarnpnpm

```
npx wrangler containers images list
```

```
yarn wrangler containers images list
```

```
pnpm wrangler containers images list
```

### Make requests to Containers

Open the URL for your Worker. It should look like `https://hello-containers.<YOUR_WORKERS_SUBDOMAIN>.workers.dev`.

* Requests to `/container/1` or `/container/2` route to specific containers. Each path after `/container/` maps to a unique container.
* Requests to `/lb` load-balance across three containers chosen at random.

Read the response body to confirm which instance handled the request. If the Worker responds but container routes still error, wait for provisioning, then check [Containers ↗](https://dash.cloudflare.com/?to=/:account/workers/containers) logs in the dashboard.

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

### Configuration

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

```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/configuration/environment-variables/) `CLOUDFLARE_DEPLOYMENT_ID.`

```go
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:

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

The `Container` class itself extends [DurableObject](https://developers.cloudflare.com/durable-objects/), so your subclass has access to the full Durable Object API. The Durable Object handles routing, lifecycle, and persistent state, while the container process runs your image inside a Linux VM. This means you can use [this.ctx.storage](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) to persist data that survives container restarts and resides close to the container itself.

Refer to the [Container class reference](https://developers.cloudflare.com/containers/reference/container-class/) and the [low-level Durable Object container API](https://developers.cloudflare.com/durable-objects/api/container/) for more details.

#### 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.  
```js  
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:  
```js  
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 running `wrangler deploy`
* Refer to [Deploy Containers](https://developers.cloudflare.com/containers/guides/deploy/) for Workers Builds and rollout behavior
* Browse [examples](https://developers.cloudflare.com/containers/examples/) for more patterns
* Check the [Frequently Asked Questions](https://developers.cloudflare.com/containers/faq/) for platform behavior and limitations

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/get-started/#page","headline":"Get started · Cloudflare Containers docs","description":"Deploy your first Container on Cloudflare by building an image, configuring a Worker, and routing requests to container instances.","url":"https://developers.cloudflare.com/containers/get-started/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Understand the key ideas behind Containers, including their lifecycle and placement.
title: Concepts
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Concepts

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/concepts/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"WebPage","@id":"https://developers.cloudflare.com/containers/concepts/#page","headline":"Concepts · Cloudflare Containers docs","description":"Understand the key ideas behind Containers, including their lifecycle and placement.","url":"https://developers.cloudflare.com/containers/concepts/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Understand how a Container is deployed, started, routed, and shut down across Cloudflare's network.
title: Lifecycle of a Container
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Lifecycle of a Container

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/concepts/architecture/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

## Deployment

After you deploy an application with a Container, your image is uploaded to [Cloudflare's Registry](https://developers.cloudflare.com/containers/guides/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.

Worker code goes live on deploy. Container instances update with a [rollout](https://developers.cloudflare.com/containers/configuration/rollouts/). Refer to [Deploy Containers](https://developers.cloudflare.com/containers/guides/deploy/).

## 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/reference/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/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

The Container class sets [sleepAfter](https://developers.cloudflare.com/containers/reference/container-class/#sleepafter) to 10 minutes by default. Its default [onActivityExpired()](https://developers.cloudflare.com/containers/reference/container-class/#onactivityexpired) implementation signals the container to stop after that period without activity. You can change the duration or override the hook.

You can stop a container instance yourself with [stop()](https://developers.cloudflare.com/containers/reference/container-class/#stop) or [destroy()](https://developers.cloudflare.com/containers/reference/container-class/#destroy).

When the platform is about to stop a container instance, it:

1. Sends `SIGTERM` to the main process in the container.
2. Waits up to 15 minutes for that process to exit.
3. Sends `SIGKILL` if the process is still running.

Handle `SIGTERM` in your image if you need cleanup before exit. The same sequence runs when a [rollout](https://developers.cloudflare.com/containers/configuration/rollouts/) replaces a container instance with a new image.

### Lifecycle hooks

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

* [onStart()](https://developers.cloudflare.com/containers/reference/container-class/#onstart) — Runs after the container has started.
* [onStop()](https://developers.cloudflare.com/containers/reference/container-class/#onstop) — Runs after the container process exits. Receives the exit code and reason for the stop.
* [onActivityExpired()](https://developers.cloudflare.com/containers/reference/container-class/#onactivityexpired) — Runs when the [sleepAfter](https://developers.cloudflare.com/containers/reference/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/reference/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.

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/concepts/architecture/#page","headline":"Lifecycle of a Container · Cloudflare Containers docs","description":"Understand how a Container is deployed, started, routed, and shut down across Cloudflare's network.","url":"https://developers.cloudflare.com/containers/concepts/architecture/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Control where your containers run with regional and jurisdictional constraints.
title: Placement
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Placement

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/concepts/placement/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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:

```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/concepts/architecture/) for more details on how placement affects container startup and routing.

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/concepts/placement/#page","headline":"Placement · Cloudflare Containers docs","description":"Control where your containers run with regional and jurisdictional constraints.","url":"https://developers.cloudflare.com/containers/concepts/placement/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Task-focused guides for deploying containers, developing locally, managing images, executing commands, and connecting over SSH.
title: Guides
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Guides

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/guides/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"WebPage","@id":"https://developers.cloudflare.com/containers/guides/#page","headline":"Guides · Cloudflare Containers docs","description":"Task-focused guides for deploying containers, developing locally, managing images, executing commands, and connecting over SSH.","url":"https://developers.cloudflare.com/containers/guides/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Deploy from your machine or Workers Builds, including how images and container instances update.
title: Deploy Containers
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Deploy Containers

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/guides/deploy/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

## Deploy from your machine

1. If `image` in your Wrangler config is a path to a Dockerfile, start [Docker ↗](https://www.docker.com/) or another Docker-compatible engine. Specify the Dockerfile itself, not its directory. If `image` is a registry reference (Cloudflare Registry, Docker Hub, Amazon ECR, or Google Artifact Registry), you do not need Docker at deploy time. Refer to [Image management](https://developers.cloudflare.com/containers/guides/image-management/).
2. From your project directory, run:  
npmyarnpnpm  
```  
npx wrangler deploy  
```  
```  
yarn wrangler deploy  
```  
```  
pnpm wrangler deploy  
```
3. Wait for the command to finish.

`wrangler deploy` uploads and activates your Worker before it processes the container configuration. For a Dockerfile image, Wrangler then builds and pushes the image when needed. For a registry image, it uses the configured image reference. These steps are not transactional: an image build, image push, or [rollout](https://developers.cloudflare.com/containers/configuration/rollouts/) error can happen after the new Worker is already live.

For an existing container application, Wrangler starts a rollout when the effective container configuration changes. The command does not wait for every container instance to be replaced, so new Worker code may briefly talk to containers that still run the previous image. The first deploy creates the container application directly, and a deploy with no effective container changes starts no rollout.

First deploy

The first deploy can take several minutes while Cloudflare provisions the image. The Worker URL may respond before container routes succeed.

For rollout flags and step configuration, refer to [Rollouts](https://developers.cloudflare.com/containers/configuration/rollouts/).

## Deploy with Workers Builds

[Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds/) runs the build and deploy commands you configure for the Worker that is connected to your Git repository.

| Git branch                                                                                                                             | Default deploy command       | Containers                                                                                                                      |
| -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Production branch                                                                                                                      | npx wrangler deploy          | Publishes the image when needed and rolls out container instances. Dockerfile builds can run in the Workers Builds environment. |
| Other branches (if [non-production branch builds](https://developers.cloudflare.com/workers/ci-cd/builds/build-branches/) are enabled) | npx wrangler versions upload | Uploads Worker code only. Does not publish a new image or roll out container instances.                                         |

### Production

1. In the Cloudflare dashboard, go to **Workers & Pages**, open the Worker you want to deploy, then go to **Settings** \> **Builds**.  
[Go to **Workers & Pages** ↗](https://dash.cloudflare.com/?to=/:account/workers-and-pages)
2. Set **Deploy command** to `npx wrangler deploy`, or to a package script that runs a full deploy.
3. Keep your Wrangler config and Dockerfile or image reference under the Workers Builds [root directory](https://developers.cloudflare.com/workers/ci-cd/builds/configuration/).
4. Push to your [production branch](https://developers.cloudflare.com/workers/ci-cd/builds/build-branches/).
5. Confirm the build succeeds, then [check the deploy](#check-your-deploy).

To connect a repository, refer to [Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds/).

### Before production

* **`wrangler deploy`** publishes container images (when needed) and rolls out container instances for the Worker you deploy.
* **`wrangler versions upload`** (the default non-production branch deploy command in Workers Builds) uploads a new Worker version only. It does not publish a new image or roll out container instances.
* **[Preview URLs](https://developers.cloudflare.com/workers/versions-and-deployments/preview-urls/) are not generated for Workers that implement [Durable Objects](https://developers.cloudflare.com/durable-objects/)**, which includes Containers Workers. A successful non-production Workers Builds run still creates a Worker version, but not a full-app preview URL.

| Goal                                                                    | What to do                                                                                                                                                                                                                                                                                                                                                                  |
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Change Worker and container together on your machine                    | [Local development](https://developers.cloudflare.com/containers/guides/local-dev/) with wrangler dev                                                                                                                                                                                                                                                                       |
| Share a deployed environment with its own image and container instances | A [Wrangler environment](https://developers.cloudflare.com/workers/wrangler/environments/) or a separate Worker, each connected to Workers Builds with a full deploy command such as npx wrangler deploy --env staging. Refer to [Workers Builds and Wrangler environments](https://developers.cloudflare.com/workers/ci-cd/builds/advanced-setups/#wrangler-environments). |
| Update production                                                       | Merge to the production branch (or run wrangler deploy locally)                                                                                                                                                                                                                                                                                                             |

Caution

Do not set the Workers Builds **non-production branch deploy command** on your production Worker to `wrangler deploy` only to try a new image from a feature branch. That command deploys the same Worker that already serves production traffic, so it can roll the container instances users already hit. Use local development, or connect a separate staging Worker or Wrangler environment to Workers Builds instead.

## Check your deploy

After `wrangler deploy` or a production Workers Builds deploy:

1. Confirm the new Worker deployment is active in the dashboard.
2. Send a request that must reach the container and confirm the behavior you expect.
3. Optionally run `npx wrangler containers list` and `npx wrangler containers images list`, or open the Containers dashboard:  
[Go to **Containers** ↗](https://dash.cloudflare.com/?to=/:account/workers/containers)

For gradual steps, grace periods, and rollout modes, refer to [Rollouts](https://developers.cloudflare.com/containers/configuration/rollouts/).

## Troubleshooting

| Problem                                                                         | What to do                                                                                                                                                                                                                                                                       |
| ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Non-production Workers Builds run succeeds but you cannot fully preview the app | Use [local development](https://developers.cloudflare.com/containers/guides/local-dev/) or a staging Worker or environment with wrangler deploy. Refer to [Before production](#before-production).                                                                               |
| Deploy or Workers Builds run succeeds but container instances look unchanged    | A gradual rollout may still be running, or the command did not update containers (versions upload or \--containers-rollout=none). Refer to [Rollouts](https://developers.cloudflare.com/containers/configuration/rollouts/).                                                     |
| Deploy fails because Docker is missing                                          | Required only when image is a Dockerfile path. Start Docker, use Workers Builds, switch to a [registry image](https://developers.cloudflare.com/containers/guides/image-management/#use-pre-built-container-images), or use \--containers-rollout=none for a Worker-only deploy. |
| First deploy: Worker works, container routes error                              | Wait several minutes for provisioning, then check logs.                                                                                                                                                                                                                          |

## Related

* [Rollouts](https://developers.cloudflare.com/containers/configuration/rollouts/)
* [Image management](https://developers.cloudflare.com/containers/guides/image-management/)
* [Local development](https://developers.cloudflare.com/containers/guides/local-dev/)
* [Workers Builds](https://developers.cloudflare.com/workers/ci-cd/builds/)
* [Wrangler environments](https://developers.cloudflare.com/workers/wrangler/environments/)

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/guides/deploy/#page","headline":"Deploy Containers · Cloudflare Containers docs","description":"Deploy from your machine or Workers Builds, including how images and container instances update.","url":"https://developers.cloudflare.com/containers/guides/deploy/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Run additional processes inside an active Container.
title: Execute commands
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Execute commands

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/guides/execute-commands/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

Use `exec()` to start another process inside a running [Container](https://developers.cloudflare.com/containers/reference/container-class/). The examples call `this.ctx.container.exec()` inside a class extending `Container` from `@cloudflare/containers`.

`exec()` does not start a stopped Container. In remote procedure call (RPC) methods, check `this.ctx.container.running` and call `await this.start()` when needed. You can also use the `onStart()` hook to run any series of commands whenever the Container starts.

## Run a process after startup

The following hook runs a preparation command whenever the Container starts. You can execute any series of startup commands from this hook. `output()` buffers standard output and standard error as separate `ArrayBuffer` values.

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

export class MyContainer extends Container {
	async onStart() {
		const process = await this.ctx.container.exec([
			"node",
			"scripts/prepare.js",
		]);
		const output = await process.output();
		const decoder = new TextDecoder();

		if (output.exitCode !== 0) {
			throw new Error(
				`Container preparation failed: ${decoder.decode(output.stderr)}`,
			);
		}

		console.log(decoder.decode(output.stdout));
	}
}
```

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

export class MyContainer extends Container {
	override async onStart() {
		const process = await this.ctx.container.exec([
			"node",
			"scripts/prepare.js",
		]);
		const output = await process.output();
		const decoder = new TextDecoder();

	if (output.exitCode !== 0) {
		throw new Error(
			`Container preparation failed: ${decoder.decode(output.stderr)}`,
		);
	}

	console.log(decoder.decode(output.stdout));
    }

}
```

In an RPC method, ensure the Container is running before calling `exec()`. Standard output uses a readable stream by default.

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

export class MyContainer extends Container {
	async readVersion() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(["node", "--version"]);
		const stdout = process.stdout
			? await new Response(process.stdout).text()
			: "";
		const exitCode = await process.exitCode;

		return { pid: process.pid, stdout, exitCode };
	}
}
```

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

export class MyContainer extends Container {
	async readVersion() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(["node", "--version"]);
		const stdout = process.stdout
			? await new Response(process.stdout).text()
			: "";
		const exitCode = await process.exitCode;

		return { pid: process.pid, stdout, exitCode };
	}
}
```

The returned `pid` identifies the new process. The `exitCode` promise resolves when that process exits.

## Pass arguments directly

The `exec()` operation starts the executable directly with the provided argument array. It does not start a shell first.

Each array item becomes one argument. Shell features such as pipes, redirects, globbing, and variable expansion do not run implicitly.

Invoke a shell when your command needs those features. Use `["bash", "-lc", "<COMMAND>"]` if Bash exists in your image. Use `["sh", "-c", "<COMMAND>"]` if the image only provides a Portable Operating System Interface (POSIX) shell. Pass untrusted values as separate arguments instead of interpolating them into a shell command string.

## Send standard input

Pass a `ReadableStream` to send existing data. Setting `stdout` to `"ignore"` discards standard output.

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

export class MyContainer extends Container {
	async importData(data) {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const stdin = new ReadableStream({
			start(controller) {
				controller.enqueue(new TextEncoder().encode(data));
				controller.close();
			},
		});
		const process = await this.ctx.container.exec(["cat"], {
			stdin,
			stdout: "ignore",
		});
		const output = await process.output();

		return {
			stdoutBytes: output.stdout.byteLength,
			exitCode: output.exitCode,
		};
	}
}
```

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

export class MyContainer extends Container {
	async importData(data: string) {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const stdin = new ReadableStream<Uint8Array>({
			start(controller) {
				controller.enqueue(new TextEncoder().encode(data));
				controller.close();
			},
		});
		const process = await this.ctx.container.exec(["cat"], {
			stdin,
			stdout: "ignore",
		});
		const output = await process.output();

		return {
			stdoutBytes: output.stdout.byteLength,
			exitCode: output.exitCode,
		};
	}
}
```

Ignored standard output produces an empty buffer from `output()`. Set `stdin` to `"pipe"` to write data over time.

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

export class MyContainer extends Container {
	async concatenateInput() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(["cat"], {
			stdin: "pipe",
		});
		const writer = process.stdin?.getWriter();

		if (!writer) {
			throw new Error("Standard input is unavailable");
		}

		const encoder = new TextEncoder();
		await writer.write(encoder.encode("first\n"));
		await writer.write(encoder.encode("second\n"));
		await writer.close();

		const output = await process.output();
		return new TextDecoder().decode(output.stdout);
	}
}
```

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

export class MyContainer extends Container {
	async concatenateInput() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(["cat"], {
			stdin: "pipe",
		});
		const writer = process.stdin?.getWriter();

		if (!writer) {
			throw new Error("Standard input is unavailable");
		}

		const encoder = new TextEncoder();
		await writer.write(encoder.encode("first\n"));
		await writer.write(encoder.encode("second\n"));
		await writer.close();

		const output = await process.output();
		return new TextDecoder().decode(output.stdout);
	}
}
```

Close the writer to send end-of-file (EOF). If you omit `stdin`, `exec()` closes standard input and sends EOF immediately.

### Pass an RPC stream to standard input

RPC methods can accept byte-oriented `ReadableStream` values whose underlying source uses `type: "bytes"`. A `Request` body meets this requirement. You can pass the received stream directly to `exec()` without buffering the entire stream in the Durable Object. For more information, refer to [Streams over RPC](https://developers.cloudflare.com/workers/runtime-apis/rpc/#readablestream-writeablestream-request-and-response).

```js
import { Container, getContainer } from "@cloudflare/containers";

export class MyContainer extends Container {
	async writeFile(input) {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(["tee", "/tmp/upload.bin"], {
			stdin: input,
			stdout: "ignore",
		});

		return process.exitCode;
	}
}

export default {
	async fetch(request, env) {
		if (!request.body) {
			return new Response("Request body required", { status: 400 });
		}

		const container = getContainer(env.MY_CONTAINER, "upload-worker");
		const exitCode = await container.writeFile(request.body);

		return Response.json({ exitCode });
	},
};
```

```ts
import { Container, getContainer } from "@cloudflare/containers";

export class MyContainer extends Container {
	async writeFile(input: ReadableStream<Uint8Array>) {
		if (!this.ctx.container.running) {
			await this.start();
		}

	const process = await this.ctx.container.exec(
		["tee", "/tmp/upload.bin"],
		{
			stdin: input,
			stdout: "ignore",
		},
	);

	return process.exitCode;
    }

}

export default {
	async fetch(request: Request, env: Env): Promise<Response> {
		if (!request.body) {
			return new Response("Request body required", { status: 400 });
		}

	const container = getContainer(env.MY_CONTAINER, "upload-worker");
	const exitCode = await container.writeFile(request.body);

	return Response.json({ exitCode });
    },

};
```

RPC transfers ownership of the stream to the Durable Object. The calling Worker cannot read it after passing it to `writeFile()`.

The following `cat` process exits because standard input is omitted:

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

export class MyContainer extends Container {
	async verifyEndOfFile() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(["cat"]);
		const output = await process.output();

		return {
			stdoutBytes: output.stdout.byteLength,
			exitCode: output.exitCode,
		};
	}
}
```

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

export class MyContainer extends Container {
	async verifyEndOfFile() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(["cat"]);
		const output = await process.output();

		return {
			stdoutBytes: output.stdout.byteLength,
			exitCode: output.exitCode,
		};
	}
}
```

## Set the process context

Use `cwd`, `env`, and `user` to set the process context. The process inherits the Container environment set by `envVars`. Per-execution `env` values add variables or override matching keys.

This example uses `sh` because it needs expansion and redirection. It also captures standard output and standard error separately.

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

export class MyContainer extends Container {
	envVars = {
		BASE_VALUE: "inherited",
		MODE: "default",
	};

	async inspectWorkspace() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(
			[
				"sh",
				"-c",
				'printf "%s:%s:%s:%s" "$PWD" "$BASE_VALUE" "$MODE" "$EXTRA_VALUE"; printf "diagnostic" >&2',
			],
			{
				cwd: "/workspace",
				env: {
					MODE: "inspection",
					EXTRA_VALUE: "added",
				},
			},
		);
		const output = await process.output();
		const decoder = new TextDecoder();

		return {
			stdout: decoder.decode(output.stdout),
			stderr: decoder.decode(output.stderr),
		};
	}
}
```

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

export class MyContainer extends Container {
	envVars = {
		BASE_VALUE: "inherited",
		MODE: "default",
	};

	async inspectWorkspace() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(
			[
				"sh",
				"-c",
				'printf "%s:%s:%s:%s" "$PWD" "$BASE_VALUE" "$MODE" "$EXTRA_VALUE"; printf "diagnostic" >&2',
			],
			{
				cwd: "/workspace",
				env: {
					MODE: "inspection",
					EXTRA_VALUE: "added",
				},
			},
		);
		const output = await process.output();
		const decoder = new TextDecoder();

		return {
			stdout: decoder.decode(output.stdout),
			stderr: decoder.decode(output.stderr),
		};
	}
}
```

The `user` option sets the user name or numeric user ID (UID) for the process. The Container runtime resolves user names from the container image.

## Combine standard error

Set `stderr` to `"combined"` to merge standard error into standard output. Combined output requires `stdout: "pipe"`.

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

export class MyContainer extends Container {
	async readCombinedOutput() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(
			[
				"bash",
				"-lc",
				'printf "standard output\n"; printf "standard error\n" >&2',
			],
			{
				stdout: "pipe",
				stderr: "combined",
			},
		);
		const output = await process.output();

		return new TextDecoder().decode(output.stdout);
	}
}
```

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

export class MyContainer extends Container {
	async readCombinedOutput() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(
			[
				"bash",
				"-lc",
				'printf "standard output\n"; printf "standard error\n" >&2',
			],
			{
				stdout: "pipe",
				stderr: "combined",
			},
		);
		const output = await process.output();

		return new TextDecoder().decode(output.stdout);
	}
}
```

The merged stream does not guarantee ordering between source streams. In this mode, `process.stderr` is `null`, and `output.stderr` is an empty `ArrayBuffer`. This example assumes Bash exists in the image.

## Handle nonzero exits

A nonzero exit code resolves `exitCode` normally. It does not reject the promise.

This example preserves standard error while ignoring standard output:

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

export class MyContainer extends Container {
	async runCheck() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(
			[
				"sh",
				"-c",
				'printf "not captured"; printf "check failed\n" >&2; exit 7',
			],
			{ stdout: "ignore" },
		);
		const output = await process.output();

		return {
			exitCode: output.exitCode,
			stdoutBytes: output.stdout.byteLength,
			stderr: new TextDecoder().decode(output.stderr),
		};
	}
}
```

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

export class MyContainer extends Container {
	async runCheck() {
		if (!this.ctx.container.running) {
			await this.start();
		}

	const process = await this.ctx.container.exec(
		[
			"sh",
			"-c",
			'printf "not captured"; printf "check failed\n" >&2; exit 7',
		],
		{ stdout: "ignore" },
	);
	const output = await process.output();

	return {
		exitCode: output.exitCode,
		stdoutBytes: output.stdout.byteLength,
		stderr: new TextDecoder().decode(output.stderr),
	};
    }

}
```

The result contains exit code `7` and the standard error text. Its ignored standard output buffer has zero bytes.

## Stream large output

`output()` buffers both streams in memory. For large output, drain `stdout` and `stderr` concurrently instead.

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

async function countBytes(stream) {
	if (!stream) {
		return 0;
	}

	let bytes = 0;
	for await (const chunk of stream) {
		bytes += chunk.byteLength;
	}
	return bytes;
}

export class MyContainer extends Container {
	async generateLargeOutput() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec([
			"sh",
			"-c",
			'i=0; while [ "$i" -lt 100000 ]; do printf "output %s\n" "$i"; printf "error %s\n" "$i" >&2; i=$((i + 1)); done',
		]);

		const [stdoutBytes, stderrBytes, exitCode] = await Promise.all([
			countBytes(process.stdout),
			countBytes(process.stderr),
			process.exitCode,
		]);

		return { stdoutBytes, stderrBytes, exitCode };
	}
}
```

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

async function countBytes(stream: ReadableStream<Uint8Array> | null) {
	if (!stream) {
		return 0;
	}

	let bytes = 0;
	for await (const chunk of stream) {
		bytes += chunk.byteLength;
	}
	return bytes;
}

export class MyContainer extends Container {
	async generateLargeOutput() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec([
			"sh",
			"-c",
			'i=0; while [ "$i" -lt 100000 ]; do printf "output %s\n" "$i"; printf "error %s\n" "$i" >&2; i=$((i + 1)); done',
		]);

		const [stdoutBytes, stderrBytes, exitCode] = await Promise.all([
			countBytes(process.stdout),
			countBytes(process.stderr),
			process.exitCode,
		]);

		return { stdoutBytes, stderrBytes, exitCode };
	}
}
```

Streaming and `output()` are alternative consumption methods. `output()` throws a `TypeError` if either stream has started being consumed. A second call to `output()` also throws a `TypeError`.

### Return standard output over RPC

Return a `ReadableStream` from an RPC method to stream output to the calling Worker. Combining standard error provides one stream for both output channels.

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

export class MyContainer extends Container {
	async streamCommandOutput() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(
			["sh", "-c", 'printf "starting\n"; run-report'],
			{ stderr: "combined" },
		);

		return process.stdout;
	}
}
```

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

export class MyContainer extends Container {
	async streamCommandOutput(): Promise<ReadableStream<Uint8Array>> {
		if (!this.ctx.container.running) {
			await this.start();
		}

	const process = await this.ctx.container.exec(
		["sh", "-c", 'printf "starting\n"; run-report'],
		{ stderr: "combined" },
	);

	return process.stdout!;
    }

}
```

RPC transfers ownership of the stream to the caller and preserves flow control. The caller must consume or cancel the stream. If the caller stops reading, backpressure can pause a process that continues writing.

This method transfers output, not the `ExecProcess` handle. Define a separate application protocol when the caller needs completion metadata or process control.

## Stop a process

`exec()` has no built-in timeout. You can request termination after a delay with `kill()` and then await `exitCode`.

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

export class MyContainer extends Container {
	async runWithTimeout() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(["sleep", "120"]);
		const timer = setTimeout(() => process.kill(), 30_000);

		try {
			return await process.exitCode;
		} finally {
			clearTimeout(timer);
		}
	}
}
```

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

export class MyContainer extends Container {
	async runWithTimeout() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(["sleep", "120"]);
		const timer = setTimeout(() => process.kill(), 30_000);

		try {
			return await process.exitCode;
		} finally {
			clearTimeout(timer);
		}
	}
}
```

Calling `kill()` without an argument queues a `SIGTERM`, signal `15`. You can pass another signal when the process requires it. A process can handle or ignore a signal, so this is not a hard execution deadline. Observe completion through `exitCode`, and do not infer a specific exit code from a signal.

## Coordinate operations

Place `exec()` calls in the Durable Object that controls the Container. The Durable Object can coordinate process state and Container lifecycle.

One application RPC method can perform multiple `exec()` operations. Each command remains a separate exec operation, but the caller makes one Durable Object RPC call. This reduces caller-to-Durable Object round trips while keeping lifecycle decisions together.

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

export class MyContainer extends Container {
	async runDiagnostics() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const commands = [
			["uname", "-a"],
			["node", "--version"],
		];
		const decoder = new TextDecoder();
		const results = [];

		for (const command of commands) {
			const process = await this.ctx.container.exec(command);
			const output = await process.output();
			results.push({
				command,
				exitCode: output.exitCode,
				stdout: decoder.decode(output.stdout),
				stderr: decoder.decode(output.stderr),
			});
		}

		return results;
	}
}
```

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

export class MyContainer extends Container {
	async runDiagnostics() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const commands = [
			["uname", "-a"],
			["node", "--version"],
		];
		const decoder = new TextDecoder();
		const results = [];

		for (const command of commands) {
			const process = await this.ctx.container.exec(command);
			const output = await process.output();
			results.push({
				command,
				exitCode: output.exitCode,
				stdout: decoder.decode(output.stdout),
				stderr: decoder.decode(output.stderr),
			});
		}

		return results;
	}
}
```

For all fields and return types, refer to the [exec() API contract](https://developers.cloudflare.com/durable-objects/api/container/#exec).

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/guides/execute-commands/#page","headline":"Execute commands · Cloudflare Containers docs","description":"Run additional processes inside an active Container.","url":"https://developers.cloudflare.com/containers/guides/execute-commands/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Learn how to use Cloudflare Registry, Docker Hub, and Amazon ECR images with Containers.
title: Image Management
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Image Management

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/guides/image-management/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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

```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/), [Amazon ECR ↗](https://aws.amazon.com/ecr/), and [Google Artifact Registry ↗](https://cloud.google.com/artifact-registry).

Note

Cloudflare does not cache images pulled from Docker Hub, Amazon ECR, or Google Artifact Registry.

Docker Hub pulls may be subject to Docker Hub pull limits or fair-use restrictions. Pulling images from Amazon ECR or Google Artifact Registry may incur cloud provider 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:

```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, Amazon ECR, or Google Artifact Registry, 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:

npmyarnpnpm

```
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:

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

```json
{
	"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:

npmyarnpnpm

```
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:

```bash
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:

```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 private Google Artifact Registry images

Configure Google Artifact Registry in Wrangler using these values:

* registry domain: `<REGION>-docker.pkg.dev`
* Google service account email flag: `--gar-email=<SERVICE_ACCOUNT_EMAIL>`
* secret: the service account JSON key

The public credential is the service account email, supplied with `--gar-email`. It must match the `client_email` field in the service account key.

The private credential is the service account JSON key. Provide it through `stdin` (a file path, raw JSON, or base64) or the interactive prompt (a file path or base64). Wrangler stores the key base64-encoded in Secrets Store.

Caution

Only `*-docker.pkg.dev` hosts are supported. Container Registry hosts such as `gcr.io` and `*.gcr.io` are not supported, because Google has shut down Container Registry.

To generate the required credentials, create a service account with the **Artifact Registry Reader** role and download its JSON key:

1. In the [Google Cloud console ↗](https://console.cloud.google.com), go to **IAM & Admin** \> **Service Accounts**.
2. Select **Create service account**, then enter a name, ID, and optional description.
3. Grant the service account the **Artifact Registry Reader** role, then select **Done**.
4. Select the service account, then open the **Keys** tab.
5. Select **Add key** \> **Create new key**, choose **JSON**, then select **Create**. The key file downloads to your machine.

Interactive: Wrangler prompts for the key, where you enter a file path or base64-encoded JSON:

npmyarnpnpm

```
npx wrangler containers registries configure <REGION>-docker.pkg.dev --gar-email=<SERVICE_ACCOUNT_EMAIL>
```

```
yarn wrangler containers registries configure <REGION>-docker.pkg.dev --gar-email=<SERVICE_ACCOUNT_EMAIL>
```

```
pnpm wrangler containers registries configure <REGION>-docker.pkg.dev --gar-email=<SERVICE_ACCOUNT_EMAIL>
```

CI or scripts: Pipe the key through `stdin` (the key contents as raw JSON or base64, or a path to the key file)

```bash
cat <PATH_TO_KEY> | npx wrangler containers registries configure <REGION>-docker.pkg.dev --gar-email=<SERVICE_ACCOUNT_EMAIL> --secret-name=<SECRET_NAME> --skip-confirmation
```

If you have already stored the key in Secrets Store, reference the existing secret and omit the key:

```bash
npx wrangler containers registries configure <REGION>-docker.pkg.dev --gar-email=<SERVICE_ACCOUNT_EMAIL> --secret-name=<EXISTING_SECRET_NAME> --skip-confirmation
```

After you configure the registry, use the fully qualified Google Artifact Registry image reference in your Wrangler configuration:

```jsonc
{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "containers": [
    {
      "image": "<REGION>-docker.pkg.dev/<PROJECT_ID>/<REPOSITORY>/<IMAGE>:<TAG>"
    }
  ]
}
```

```toml
[[containers]]
image = "<REGION>-docker.pkg.dev/<PROJECT_ID>/<REPOSITORY>/<IMAGE>:<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:

```bash
docker pull <PUBLIC_IMAGE>
docker tag <PUBLIC_IMAGE> <IMAGE>:<TAG>
```

Wrangler provides a command to push images to the Cloudflare Registry:

npmyarnpnpm

```
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:

npmyarnpnpm

```
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:

```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, Amazon ECR, and Google Artifact Registry are supported in local development.

With `vite dev`, image references from external registries such as Docker Hub, Amazon ECR, and Google Artifact Registry are supported, but `vite dev` cannot pull directly from the Cloudflare Registry.

If you use a private Docker Hub, Amazon ECR, or Google Artifact Registry image in local development, 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/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.

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/guides/image-management/#page","headline":"Image Management · Cloudflare Containers docs","description":"Learn how to use Cloudflare Registry, Docker Hub, and Amazon ECR images with Containers.","url":"https://developers.cloudflare.com/containers/guides/image-management/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Learn how to run Container-enabled Workers locally with `wrangler dev` and `vite dev`.
title: Local Development
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Local Development

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/guides/local-dev/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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, Amazon ECR, or Google Artifact Registry.

Note

With `wrangler dev`, image references from the Cloudflare Registry, Docker Hub, Amazon ECR, and Google Artifact Registry are supported in local development.

With `vite dev`, image references from external registries such as Docker Hub, Amazon ECR, and Google Artifact Registry are supported, but `vite dev` cannot pull directly from the Cloudflare Registry.

If you use a private Docker Hub, Amazon ECR, or Google Artifact Registry image in local development, 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.

## FUSE support

Miniflare automatically grants local containers the Docker privileges required for Filesystem in Userspace (FUSE). This applies to `wrangler dev`, the Cloudflare Vite plugin, and direct Miniflare use.

Miniflare grants these privileges when the local Docker daemon runs inside a virtual machine (VM). This includes Docker engines on macOS and through Windows Subsystem for Linux (WSL). On Linux, Miniflare grants the privileges for local rootless Docker when `/dev/fuse` is available.

Rootful Docker on Linux does not support FUSE by default during local development. Miniflare does not grant FUSE privileges when the Docker daemon does not meet these conditions or cannot be inspected.

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

```txt
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:

```txt
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.  
```dockerfile  
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`.

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/guides/local-dev/#page","headline":"Local Development · Cloudflare Containers docs","description":"Learn how to run Container-enabled Workers locally with wrangler dev and vite dev.","url":"https://developers.cloudflare.com/containers/guides/local-dev/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Intercept and handle outbound HTTP from containers using Workers.
title: Handle outbound traffic
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Handle outbound traffic

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/guides/outbound-traffic/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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/configuration/workers-connections/) like KV, R2, and Durable Objects

## Block outbound traffic

Use `enableInternet = false` to block public internet access by default:

```js
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:

```js
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:

```js
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:

```js
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:

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

```js
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:

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

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

```js
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(" && "),
	];
}
```

```js
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(" && "),
	];
}
```

```js
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(" && "),
	];
}
```

```js
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:

```js
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:

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

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

```js
// 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`.

```js
// 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/configuration/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/configuration/environment-variables/) — Configure secrets and environment variables
* [Durable Object interface](https://developers.cloudflare.com/durable-objects/api/container/) — Full `ctx.container` API reference

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/guides/outbound-traffic/#page","headline":"Handle outbound traffic · Cloudflare Containers docs","description":"Intercept and handle outbound HTTP from containers using Workers.","url":"https://developers.cloudflare.com/containers/guides/outbound-traffic/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Connect to running container instances with SSH.
title: SSH
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# SSH

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/guides/ssh/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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:

```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:

```bash
wrangler containers ssh <INSTANCE_ID>
```

## Use as SSH proxy

You can use `wrangler containers ssh` as an OpenSSH `ProxyCommand`. This lets your local SSH client connect through Wrangler.

```sh
ssh -o ProxyCommand="wrangler containers ssh %h" cloudchamber@<INSTANCE_ID>
```

When used this way, Wrangler pipes standard input and output to the SSH server in the running Container. You can also pass `--stdio` to force this mode.

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

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/guides/ssh/#page","headline":"SSH · Cloudflare Containers docs","description":"Connect to running container instances with SSH.","url":"https://developers.cloudflare.com/containers/guides/ssh/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Configure Containers — connect them to Workers and bindings, set environment variables, tune scaling and routing, and manage rollouts.
title: Configuration
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Configuration

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/configuration/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"WebPage","@id":"https://developers.cloudflare.com/containers/configuration/#page","headline":"Configuration · Cloudflare Containers docs","description":"Configure Containers — connect them to Workers and bindings, set environment variables, tune scaling and routing, and manage rollouts.","url":"https://developers.cloudflare.com/containers/configuration/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

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

[Skip to content](#main-content)

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

# Environment Variables

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/configuration/environment-variables/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/configuration/environment-variables/#page","headline":"Environment Variables · Cloudflare Containers docs","description":"Runtime and user-defined environment variables available inside Container instances.","url":"https://developers.cloudflare.com/containers/configuration/environment-variables/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: How container instances update after a deploy, including step percentages, grace periods, and rollout modes.
title: Rollouts
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Rollouts

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/configuration/rollouts/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

## How rollouts work

A **rollout** applies a target container application configuration after you [deploy](https://developers.cloudflare.com/containers/guides/deploy/) a Worker that uses Containers. The target can change the image, instance type, limits, placement, or other container settings.

A **container instance** is one running copy of your container image on Cloudflare's network. It runs the process your image starts (`ENTRYPOINT`/`CMD` in the Dockerfile, or the base image default). When the target changes the image, the rollout replaces container instances with copies that run the target image. Rollouts do not change Durable Object storage.

When an existing container application's effective configuration changes, `wrangler deploy`:

1. Uploads and activates the new Worker version, including Durable Object class code.
2. Builds and pushes a Dockerfile image when needed, or uses the configured registry image reference.
3. Starts a rollout to apply the target container configuration.

The Worker is active before the image and rollout steps begin. These steps are not transactional, so the Worker can remain active if a later image or rollout step fails. Deploy success means the rollout started, not that every container instance has finished replacing. The first deploy creates the container application directly, and a deploy with no effective container changes starts no rollout.

When the image changes, new Worker code can still reach container instances on the previous image until the rollout finishes. Prefer Worker and image changes that work together during that window, or choose [immediate](#immediate) when you need the shortest mixed window the platform allows.

Field names and allowed values are listed under [Containers configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#containers).

## Defaults

| Setting                                           | Default                                                                         |
| ------------------------------------------------- | ------------------------------------------------------------------------------- |
| rollout\_step\_percentage                         | 100 if max\_instances is omitted or less than 2; otherwise \[10, 100\]          |
| rollout\_active\_grace\_period                    | 0 seconds                                                                       |
| Stop sequence when replacing a container instance | SIGTERM to the main process, then SIGKILL after 15 minutes if it has not exited |

## Gradual rollouts

By default, Wrangler starts a rolling rollout using `rollout_step_percentage`. If `max_instances` is omitted or less than `2`, Wrangler uses one `100` step. Otherwise, Wrangler requests `[10, 100]`:

1. Request a target of about 10% of container instances with the new configuration. The platform raises this percentage when necessary so the step represents at least one instance at the configured `max_instances`.
2. Target 100% of container instances with the new configuration.

Configure the steps with `rollout_step_percentage` in Wrangler. Override the default plan for one deploy with [\--containers-rollout](#rollout-modes).

## How a container instance is replaced

When the rollout selects a container instance to update:

1. **Grace period (if configured).** If `rollout_active_grace_period` is greater than `0`, container instances that only recently became connected to their [Durable Object](https://developers.cloudflare.com/durable-objects/) are skipped until they pass that window. Default `0` means no extra wait. Refer to [Rollout active grace period](#rollout-active-grace-period).
2. **Signal stop.** The platform sends `SIGTERM` to the main process in the container so it can stop accepting new work and finish in-flight work. Handle `SIGTERM` in your image if that process needs cleanup before exit.
3. **Drain.** The process has up to 15 minutes to exit after `SIGTERM`.
4. **Force stop if needed.** If the process is still running after 15 minutes, the platform sends `SIGKILL`.
5. **After exit.** The Container class [onStop](https://developers.cloudflare.com/containers/reference/container-class/#onstop) hook can run in the Worker once the container process has exited.
6. **Start a new container instance** with the target image. Disk is [ephemeral](https://developers.cloudflare.com/containers/faq/#is-disk-persistent-what-happens-to-my-disk-when-my-container-sleeps) unless you store data outside the container filesystem.

Each selected container instance follows this sequence on its own schedule. The fleet does not restart in a single moment.

### Requests while a container instance starts

The new container instance must start its process. Startup often takes on the order of seconds, depending on image size and what runs at start. Refer to [cold starts](https://developers.cloudflare.com/containers/concepts/architecture/#starting-a-container).

A request that needs that container instance may wait until the container is ready, or fail if a client or Worker timeout is shorter than startup. Keep startup work fast, use port readiness checks if you configure them, and set timeouts with startup in mind.

## Rollout active grace period

`rollout_active_grace_period` applies only during a rollout, when the platform chooses which container instances to replace.

Containers are [backed by Durable Objects](https://developers.cloudflare.com/containers/concepts/architecture/#worker-to-durable-object). Each running container instance is associated with a Durable Object instance that starts it and sends it traffic. The grace period is how long that connection must already have been up before a rollout may shut the container down. It is not measured from deploy completion.

| Value                            | Effect                                                                                                                           |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| 0 (default)                      | No extra protection. Selected container instances may be replaced as soon as the rollout reaches them.                           |
| Greater than 0 (for example 300) | Container instances connected to their Durable Object for less than this many seconds are left alone until they pass the window. |

Use a non-zero value when short sessions should finish before a rollout replaces the container. Container instances that have been connected longer can still be replaced once they pass the window.

`rollout_active_grace_period` applies in every rollout mode, including [immediate](#immediate).

## Rollout modes

`--containers-rollout` applies to [wrangler deploy](https://developers.cloudflare.com/workers/wrangler/commands/workers/#deploy) only. It does not apply to [wrangler versions upload](https://developers.cloudflare.com/workers/wrangler/commands/workers/#versions).

On a full deploy, Wrangler activates the Worker before it processes the container image and rollout. Rollout mode controls how the target container configuration is applied.

| Mode              | Flag                            | Container instances                                                             |
| ----------------- | ------------------------------- | ------------------------------------------------------------------------------- |
| Gradual (default) | omit flag                       | Use rollout\_step\_percentage, which can contain one or multiple steps          |
| Immediate         | \--containers-rollout=immediate | Target 100% of container instances in one step                                  |
| None              | \--containers-rollout=none      | Leave images and running container instances unchanged; deploy Worker code only |

### Immediate

Immediate sets the rollout plan to a single step that targets 100% of container instances. There is no intermediate percentage hold.

npmyarnpnpm

```
npx wrangler deploy --containers-rollout=immediate
```

```
yarn wrangler deploy --containers-rollout=immediate
```

```
pnpm wrangler deploy --containers-rollout=immediate
```

Use immediate when Worker code and the container image need to stay compatible and you want the mixed window as short as the platform allows (for example a breaking change in how the Worker talks to the process in the image).

Behavior:

* The new Worker version is activated before the container image and rollout are processed.
* The rollout then replaces container instances toward 100% using the same [replace sequence](#how-a-container-instance-is-replaced) as gradual mode, including grace period when configured.
* Replacements complete over wall-clock time. How long depends on how many container instances are running, how long each takes to stop and start, and any grace period.
* When the image changes, immediate minimizes but does not eliminate the period when the new Worker can reach instances on the previous image.
* Deploy success means the rollout started, not that replacements finished.

### None

None leaves images and running container instances unchanged and deploys Worker code only.

Use none when the deploy should not publish a new image or start a container instance rollout. If `image` is a Dockerfile path and Docker is unavailable, Wrangler may require this flag or a working Docker setup so the deploy can skip container steps.

## Example configuration

`rollout_active_grace_period` of 300 seconds (five minutes) and steps `[10, 100]`:

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

## Related

* [Deploy Containers](https://developers.cloudflare.com/containers/guides/deploy/)
* [Lifecycle of a Container](https://developers.cloudflare.com/containers/concepts/architecture/)
* [Image management](https://developers.cloudflare.com/containers/guides/image-management/)
* [Containers configuration](https://developers.cloudflare.com/workers/wrangler/configuration/#containers)

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/configuration/rollouts/#page","headline":"Rollouts · Cloudflare Containers docs","description":"How container instances update after a deploy, including step percentages, grace periods, and rollout modes.","url":"https://developers.cloudflare.com/containers/configuration/rollouts/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Scale Container instances using explicit IDs or the getRandom helper for stateless load balancing.
title: Scaling and Routing
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Scaling and Routing

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/configuration/scaling-and-routing/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

## Scale container instances with explicit IDs

Note

This section uses helpers from the [Container class](https://developers.cloudflare.com/containers/reference/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.

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/configuration/scaling-and-routing/#page","headline":"Scaling and Routing · Cloudflare Containers docs","description":"Scale Container instances using explicit IDs or the getRandom helper for stateless load balancing.","url":"https://developers.cloudflare.com/containers/configuration/scaling-and-routing/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Access KV, R2, Durable Objects, and other bindings from a container.
title: Connect to Workers and Bindings
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Connect to Workers and Bindings

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/configuration/workers-connections/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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

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

```js
"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/guides/outbound-traffic/) — Block, allow, and intercept all outbound HTTP from a container
* [Environment variables and secrets](https://developers.cloudflare.com/containers/configuration/environment-variables/) — Configure secrets and environment variables
* [Durable Object interface](https://developers.cloudflare.com/durable-objects/api/container/) — Full `ctx.container` API reference

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/configuration/workers-connections/#page","headline":"Connect to Workers and Bindings · Cloudflare Containers docs","description":"Access KV, R2, Durable Objects, and other bindings from a container.","url":"https://developers.cloudflare.com/containers/configuration/workers-connections/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Code examples showing how to use Containers with Workers for stateless routing, cron jobs, WebSockets, and more.
title: Examples
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Examples

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/examples/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

Explore the following examples of Container functionality:

[**Mount R2 buckets with FUSE**Mount R2 buckets as filesystems using FUSE in Containers](https://developers.cloudflare.com/containers/examples/r2-fuse-mount/)

[**Static Frontend, Container Backend**A simple frontend app with a containerized backend](https://developers.cloudflare.com/containers/examples/container-backend/)

[**Cron Container**Running a container on a schedule using Cron Triggers](https://developers.cloudflare.com/containers/examples/cron/)

[**Using Durable Objects Directly**Various examples calling Containers directly from Durable Objects](https://developers.cloudflare.com/containers/examples/durable-object-interface/)

[**Env Vars and Secrets**Pass in environment variables and secrets to your container](https://developers.cloudflare.com/containers/examples/env-vars-and-secrets/)

[**Stateless Instances**Run multiple instances across Cloudflare's network](https://developers.cloudflare.com/containers/examples/stateless/)

[**Status Hooks**Execute Workers code in reaction to Container status changes](https://developers.cloudflare.com/containers/examples/status-hooks/)

[**Websocket to Container**Forwarding a Websocket request to a Container](https://developers.cloudflare.com/containers/examples/websocket/)

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"WebPage","@id":"https://developers.cloudflare.com/containers/examples/#page","headline":"Examples · Cloudflare Containers docs","description":"Code examples showing how to use Containers with Workers for stateless routing, cron jobs, WebSockets, and more.","url":"https://developers.cloudflare.com/containers/examples/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: A simple frontend app with a containerized backend
title: Static Frontend, Container Backend
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Static Frontend, Container Backend

A simple frontend app with a containerized backend

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/examples/container-backend/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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

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

```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/configuration/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

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

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/examples/container-backend/#page","headline":"Static Frontend, Container Backend · Cloudflare Containers docs","description":"A simple frontend app with a containerized backend","url":"https://developers.cloudflare.com/containers/examples/container-backend/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Running a container on a schedule using Cron Triggers
title: Cron Container
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Cron Container

Running a container on a schedule using Cron Triggers

Last updated Apr 21, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/examples/cron/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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:

```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:

```ts
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(),
      }
    })
  },
};
```

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/examples/cron/#page","headline":"Cron Container · Cloudflare Containers docs","description":"Running a container on a schedule using Cron Triggers","url":"https://developers.cloudflare.com/containers/examples/cron/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Pass in environment variables and secrets to your container
title: Env Vars and Secrets
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Env Vars and Secrets

Pass in environment variables and secrets to your container

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/examples/env-vars-and-secrets/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

Environment variables can be passed into a Container using the `envVars` field in the [Container](https://developers.cloudflare.com/containers/reference/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:

npmyarnpnpm

```
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:

npmyarnpnpm

```
npx wrangler secrets-store store create demo --remote
```

```
yarn wrangler secrets-store store create demo --remote
```

```
pnpm wrangler secrets-store store create demo --remote
```

npmyarnpnpm

```
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:

npmyarnpnpm

```
npx wrangler kv namespace create DEMO_KV
```

```
yarn wrangler kv namespace create DEMO_KV
```

```
pnpm wrangler kv namespace create DEMO_KV
```

npmyarnpnpm

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

```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:

```js
// 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.

```js
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

```js
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

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

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/examples/env-vars-and-secrets/#page","headline":"Env Vars and Secrets · Cloudflare Containers docs","description":"Pass in environment variables and secrets to your container","url":"https://developers.cloudflare.com/containers/examples/env-vars-and-secrets/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Mount R2 buckets as filesystems using FUSE in Containers
title: Mount R2 buckets with FUSE
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Mount R2 buckets with FUSE

Mount R2 buckets as filesystems using FUSE in Containers

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/examples/r2-fuse-mount/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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.

To run a FUSE container locally, refer to [FUSE support during local development](https://developers.cloudflare.com/containers/guides/local-dev/#fuse-support).

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

```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:

```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,
	};
}
```

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

```json
{
  "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:

```dockerfile
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:

```dockerfile
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

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/examples/r2-fuse-mount/#page","headline":"Mount R2 buckets with FUSE · Cloudflare Containers docs","description":"Mount R2 buckets as filesystems using FUSE in Containers","url":"https://developers.cloudflare.com/containers/examples/r2-fuse-mount/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Run multiple instances across Cloudflare's network
title: Stateless Instances
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Stateless Instances

Run multiple instances across Cloudflare's network

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/examples/stateless/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

To simply proxy requests to one of multiple instances of a container, you can use the `getRandom` function:

```ts
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/configuration/scaling-and-routing) for more details.

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/examples/stateless/#page","headline":"Stateless Instances · Cloudflare Containers docs","description":"Run multiple instances across Cloudflare's network","url":"https://developers.cloudflare.com/containers/examples/stateless/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Execute Workers code in reaction to Container status changes
title: Status Hooks
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Status Hooks

Execute Workers code in reaction to Container status changes

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/examples/status-hooks/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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/reference/container-class/#lifecycle-hooks) for more details.

```ts
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);
	}
}
```

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/examples/status-hooks/#page","headline":"Status Hooks · Cloudflare Containers docs","description":"Execute Workers code in reaction to Container status changes","url":"https://developers.cloudflare.com/containers/examples/status-hooks/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Forwarding a Websocket request to a Container
title: Websocket to Container
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Websocket to Container

Forwarding a Websocket request to a Container

Last updated Apr 21, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/examples/websocket/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

WebSocket requests are automatically forwarded to a container using the default `fetch`method on the `Container` class:

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

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/examples/websocket/#page","headline":"Websocket to Container · Cloudflare Containers docs","description":"Forwarding a Websocket request to a Container","url":"https://developers.cloudflare.com/containers/examples/websocket/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-04-21","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Lookup details for the Containers platform, including the Container class, the Durable Object interface, and Wrangler configuration and commands.
title: Reference
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Reference

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/reference/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"WebPage","@id":"https://developers.cloudflare.com/containers/reference/#page","headline":"Reference · Cloudflare Containers docs","description":"Lookup details for the Containers platform, including the Container class, the Durable Object interface, and Wrangler configuration and commands.","url":"https://developers.cloudflare.com/containers/reference/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: API reference for the Container interface and utility functions
title: Container Interface
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Container Interface

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/reference/container-class/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

The [Container class ↗](https://github.com/cloudflare/containers) from [@cloudflare/containers ↗](https://www.npmjs.com/package/@cloudflare/containers) is the most common way to interact with container instances from a Worker.

**`Container` extends [DurableObject](https://developers.cloudflare.com/durable-objects/api/base/).** The Durable Object manages routing, persistent state, and lifecycle hooks, while the container process runs your image inside a Linux VM. Because your subclass is a Durable Object, you have access to the full Durable Object API — including [this.ctx.storage](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) for persistent SQLite-backed storage and [this.ctx.id](https://developers.cloudflare.com/durable-objects/api/id/) for the unique instance identifier. Use Durable Object storage to persist state that should survive container restarts, such as configuration, user data, or task results.

npmyarnpnpmbun

```
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:

```js
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);
	},
};
```

```plaintext
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 — including [SQLite storage](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/), [alarms](https://developers.cloudflare.com/durable-objects/api/alarms/), and [RPC methods](https://developers.cloudflare.com/durable-objects/api/base/#rpc-methods). Container disk is ephemeral by default, but Durable Object storage persists across container restarts.

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

export class MyContainer extends Container {
	defaultPort = 8080;

	async runAndPersist() {
		const res = await this.containerFetch("/run-task");
		const body = await res.text();
		this.ctx.storage.sql.exec(
			"INSERT OR REPLACE INTO results (value) VALUES (?)",
			body,
		);
		return body;
	}
}
```

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

export class MyContainer extends Container {
	defaultPort = 8080;

	async runAndPersist() {
		const res = await this.containerFetch("/run-task");
		const body = await res.text();
		this.ctx.storage.sql.exec(
			"INSERT OR REPLACE INTO results (value) VALUES (?)",
			body,
		);
		return body;
	}
}
```

## Execute commands

Use `this.ctx.container.exec()` to start another process inside a running Container. Refer to [Execute commands](https://developers.cloudflare.com/containers/guides/execute-commands/) for startup, streaming, output, and process-control examples.

## 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/guides/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.

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

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

export class MyContainer extends Container {
	defaultPort = 8080;

	async onStart() {
		await this.containerFetch("http://localhost/bootstrap", {
			method: "POST",
		});
	}
}
```

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

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

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

export class MyContainer extends Container {
	onStop({ exitCode, reason }) {
		console.log("Container stopped", { exitCode, reason });
	}
}
```

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

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

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

export class MyContainer extends Container {
	onError(error) {
		console.error("Container failed to start", error);
		throw error;
	}
}
```

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

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

Caution

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.

```js
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();
	}
}
```

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

```ts
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:

```js
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);
	}
}
```

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

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

```js
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);
	}
}
```

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

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

```js
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,
			},
		});
	},
};
```

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

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

```js
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,
		});
	},
};
```

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

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

```js
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);
	}
}
```

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

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

```js
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);
	}
}
```

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

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

```js
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);
	}
}
```

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

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

```js
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);
	}
}
```

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

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

```js
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",
			});
		}
	}
}
```

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

```ts
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:

```js
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");
	}
}
```

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

```js
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);
	},
};
```

```plaintext
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/guides/outbound-traffic/).

## Utility functions

These functions are exported alongside the `Container` class from `@cloudflare/containers`.

### `getContainer`

Get a stub for a named container instance.

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

```js
import { getContainer } from "@cloudflare/containers";

export default {
	async fetch(request, env) {
		const { sessionId } = await request.json();
		return getContainer(env.MY_CONTAINER, sessionId).fetch(request);
	},
};
```

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

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

```js
import { getRandom } from "@cloudflare/containers";

export default {
	async fetch(request, env) {
		const container = await getRandom(env.WORKER_POOL, 5);
		return container.fetch(request);
	},
};
```

```plaintext
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()`.

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

```js
import { getContainer, switchPort } from "@cloudflare/containers";

export default {
	async fetch(request, env) {
		const container = getContainer(env.MY_CONTAINER);
		return container.fetch(switchPort(request, 9090));
	},
};
```

```plaintext
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));
	},
};
```

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/reference/container-class/#page","headline":"Container Interface · Cloudflare Containers docs","description":"API reference for the Container interface and utility functions","url":"https://developers.cloudflare.com/containers/reference/container-class/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Access and manage containers associated with a Durable Object, including start, stop, and interaction methods.
title: Durable Object Container
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Durable Object Container

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/durable-objects/api/container/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

## Description

Each [container](https://developers.cloudflare.com/containers/) is managed by a Durable Object. The [Container class](https://developers.cloudflare.com/containers/reference/container-class/) from `@cloudflare/containers` extends `DurableObject` and handles lifecycle management, port readiness, and sleep timeouts for you. The Durable Object manages routing, persistent state, and lifecycle hooks, while the container process runs your image inside a Linux VM.

The low-level API documented on this page is available on `this.ctx.container` inside any Durable Object class that has a container binding. Use it when you need direct control over the container process or cannot use the `Container` class.

Because the `Container` class extends `DurableObject`, you also have access to [SQLite storage](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) via `this.ctx.storage`, [alarms](https://developers.cloudflare.com/durable-objects/api/alarms/), and all other Durable Object APIs.

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

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

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

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

### `exec`

`exec` starts another process inside an already-running Container. It does not start a stopped Container.

The following example calls `this.ctx.container.exec()` inside a class extending `Container` from `@cloudflare/containers`. In RPC methods, check `this.ctx.container.running` and call `await this.start()` when needed. You can also use the `onStart()` hook to run any series of commands whenever the Container starts.

```ts
exec(
  cmd: string[],
  options?: ContainerExecOptions,
): Promise<ExecProcess>
```

The `exec` operation starts the executable directly with the provided arguments. It does not start a shell or interpret pipes, redirects, expansion, or other shell syntax. Invoke Bash explicitly with `["bash", "-lc", "<COMMAND>"]` when Bash exists in the image. Use `["sh", "-c", "<COMMAND>"]` for images with only a Portable Operating System Interface (POSIX) shell.

The following RPC method starts the Container before executing a command:

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

export class MyContainer extends Container {
	async runCommand() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(["node", "--version"]);
		const output = await process.output();

		return {
			pid: process.pid,
			exitCode: output.exitCode,
			stdout: new TextDecoder().decode(output.stdout),
		};
	}
}
```

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

export class MyContainer extends Container {
	async runCommand() {
		if (!this.ctx.container.running) {
			await this.start();
		}

		const process = await this.ctx.container.exec(["node", "--version"]);
		const output = await process.output();

		return {
			pid: process.pid,
			exitCode: output.exitCode,
			stdout: new TextDecoder().decode(output.stdout),
		};
	}
}
```

#### Parameters

* `cmd` (`string[]`) — executable followed by its arguments.
* `options` (`ContainerExecOptions`, optional) — process configuration:  
  * `stdin` (`ReadableStream | "pipe"`) — source for standard input. Use `"pipe"` to write through the returned `stdin` stream. When omitted, standard input closes and sends end-of-file (EOF).
  * `stdout` (`"pipe" | "ignore"`, default `"pipe"`) — captures or discards standard output.
  * `stderr` (`"pipe" | "ignore" | "combined"`, default `"pipe"`) — captures, discards, or merges standard error into standard output. The `"combined"` value requires `stdout: "pipe"`. Combined output does not guarantee ordering between its source streams.
  * `cwd` (`string`) — working directory for the process.
  * `env` (`Record<string, string>`) — environment additions and overrides. The process inherits existing Container variables. Matching keys use the per-execution value.
  * `user` (`string`) — image user for the process.

#### Return values

Returns `Promise<ExecProcess>`.

An `ExecProcess` has these fields and methods:

* `stdin` (`WritableStream | null`) — writable standard input when `stdin` is `"pipe"`.
* `stdout` (`ReadableStream | null`) — readable standard output when piped.
* `stderr` (`ReadableStream | null`) — readable standard error when piped separately.
* `pid` (`number`) — process identifier.
* `exitCode` (`Promise<number>`) — resolves when the process exits. Nonzero codes resolve normally instead of rejecting.
* `output()` (`Promise<ExecOutput>`) — reads buffered output once. `ExecOutput` contains `stdout` (`ArrayBuffer`), `stderr` (`ArrayBuffer`), and `exitCode` (`number`). Ignored streams produce empty buffers. Use `TextDecoder` to decode text.
* `kill(signal?: number)` (`void`) — queues a signal for the process. The default is `SIGTERM`, signal `15`. The signal must be from `1` through `64`.

With `stderr: "combined"`, `stderr` is `null` on `ExecProcess` and an empty `ArrayBuffer` on `ExecOutput`. Read both output channels from `stdout`.

`output()` throws a `TypeError` when called more than once or after either readable stream starts being consumed. For large output, consume both readable streams concurrently instead of buffering them with `output()`.

`exec` has no built-in timeout. Use `kill()` to request termination, then observe completion through `exitCode`. A process can handle or ignore a signal, so this does not enforce a hard deadline. Do not infer a specific exit code from the signal.

#### Exceptions

* `exec()` throws when the Container is not running.
* `exec()` throws a `TypeError` when `cmd` is empty, an option mode is invalid, or `stderr: "combined"` is used with `stdout: "ignore"`.
* `exec()` rejects if the runtime cannot create or start the process.
* Environment variable names cannot contain `=` or null characters. Environment values, `cwd`, and `user` cannot contain null characters.
* `kill()` throws a `RangeError` when the signal is outside the supported range.

For task-oriented examples, refer to [Execute commands](https://developers.cloudflare.com/containers/guides/execute-commands/).

### `destroy`

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

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

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

```js
const port = this.ctx.container.getTcpPort(8080);
const res = await port.fetch("http://container/set-state", {
	body: initialState,
	method: "POST",
});
```

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

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

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

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

```js
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

* [Container class reference](https://developers.cloudflare.com/containers/reference/container-class/) — the recommended high-level API built on top of this interface
* [Containers overview](https://developers.cloudflare.com/containers/)
* [Get started with Containers](https://developers.cloudflare.com/containers/get-started/)
* [SQLite storage API](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/) — persist state across container restarts
* [Durable Objects](https://developers.cloudflare.com/durable-objects/) — the underlying platform that powers Containers

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/durable-objects/api/container/#page","headline":"Durable Object Container · Cloudflare Durable Objects docs","description":"Access and manage containers associated with a Durable Object, including start, stop, and interaction methods.","url":"https://developers.cloudflare.com/durable-objects/api/container/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Wrangler commands for interacting with Cloudflare's Container Platform.
title: Containers
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Containers

Last updated Apr 23, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/workers/wrangler/commands/containers/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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

### `build`

Build a Container image from a Dockerfile.

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

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

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

```txt
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/guides/image-management/#using-amazon-ecr-container-images) about our currently supported external registries.

#### `registries list`

List registries your containers are able to use.

```txt
wrangler containers registries list [OPTIONS]
```

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

#### `registries configure`

Configure a new registry for your account.

```txt
wrangler containers registries configure [DOMAIN] [OPTIONS]
```

* `DOMAIN` `string` required  
  * Domain to configure for the registry.
* `--dockerhub-username` `string` optional  
  * The Docker Hub username to authenticate with. Use with the `docker.io` domain. The secret is a Docker Hub personal access token.
* `--aws-access-key-id` `string` optional  
  * The AWS access key ID to authenticate with. Use with an Amazon ECR domain. The secret is the matching AWS secret access key.
* `--gar-email` `string` optional  
  * The Google service account email to authenticate with. Use with a `*-docker.pkg.dev` domain.
* `--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

The credential flags are mutually exclusive. Use the one that matches the registry you are configuring.

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.

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

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

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

```txt
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:

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

```sh
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:

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

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

```txt
wrangler containers list [OPTIONS]
```

### `push`

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

```txt
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/guides/ssh/) for configuration details.

```txt
wrangler containers ssh <INSTANCE_ID>
```

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

```txt
wrangler containers ssh <INSTANCE_ID> -- ls -al
```

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

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/workers/wrangler/commands/containers/#page","headline":"Containers · Cloudflare Workers docs","description":"Wrangler commands for interacting with Cloudflare's Container Platform.","url":"https://developers.cloudflare.com/workers/wrangler/commands/containers/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-04-23","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Product-wide information for Containers, including pricing and limits.
title: Platform
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Platform

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/platform/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"WebPage","@id":"https://developers.cloudflare.com/containers/platform/#page","headline":"Platform · Cloudflare Containers docs","description":"Product-wide information for Containers, including pricing and limits.","url":"https://developers.cloudflare.com/containers/platform/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Available Container instance types and account-level limits for memory, vCPU, disk, and image storage.
title: Limits and Instance Types
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Limits and Instance Types

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/platform/limits/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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

## Account limits

The following limits apply per account:

| Resource                        | Limit                                          |
| ------------------------------- | ---------------------------------------------- |
| Concurrent memory               | 6 TiB                                          |
| Concurrent vCPU                 | 1,500                                          |
| Concurrent disk                 | 30 TB                                          |
| 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/versions-and-deployments/rollbacks/) your Worker to a previous version, this version may no longer work. [↩](#user-content-fnref-1)

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/platform/limits/#page","headline":"Limits and Instance Types · Cloudflare Containers docs","description":"Available Container instance types and account-level limits for memory, vCPU, disk, and image storage.","url":"https://developers.cloudflare.com/containers/platform/limits/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

---
description: Billing rates for Containers vCPU, memory, disk, and network egress, including included usage on the Workers Paid plan.
title: Pricing
image: https://developers.cloudflare.com/og-docs.png
---

[Skip to content](#main-content)

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

# Pricing

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/platform/pricing/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

## 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/limits/#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.

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/platform/pricing/#page","headline":"Pricing · Cloudflare Containers docs","description":"Billing rates for Containers vCPU, memory, disk, and network egress, including included usage on the Workers Paid plan.","url":"https://developers.cloudflare.com/containers/platform/pricing/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```

---

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

[Skip to content](#main-content)

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

# Frequently Asked Questions

Last updated Aug 28, 2026|Copy as Markdown|[View as Markdown](https://developers.cloudflare.com/containers/faq/index.md)|[Agent setup](https://developers.cloudflare.com/agent-setup/)

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

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

On `wrangler deploy`, the Worker goes live first. Container instances update with a gradual rollout by default. Refer to [Rollouts](https://developers.cloudflare.com/containers/configuration/rollouts/) for steps, grace periods, and modes. Refer to [Deploy Containers](https://developers.cloudflare.com/containers/guides/deploy/) to run a deploy.

## How do Workers Builds work with Containers?

On the production branch, Workers Builds should run `wrangler deploy` so images and container instances can update. Non-production Workers Builds defaults to `wrangler versions upload`, which does not update images. Containers Workers implement Durable Objects, so preview URLs are not generated for them. Refer to [Deploy Containers](https://developers.cloudflare.com/containers/guides/deploy/#before-production).

## 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/configuration/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?

Refer to [image management](https://developers.cloudflare.com/containers/guides/image-management/#use-pre-built-container-images).

## 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 does not stop a container instance after a fixed maximum runtime. The Container class sets [sleepAfter](https://developers.cloudflare.com/containers/reference/container-class/#sleepafter) to 10 minutes by default, and its default [onActivityExpired()](https://developers.cloudflare.com/containers/reference/container-class/#onactivityexpired) implementation signals the container to stop after that period without activity. You can change the duration or override the hook. Even if your hook keeps the instance running, another platform event can stop it. One of those cases is a host server restart, which happens on an irregular cadence. Cloudflare does not guarantee that any container instance will run for any set period of time.

When the platform is about to stop a container instance (including before a host moves work off a server), it:

1. Sends `SIGTERM` to the main process in the container.
2. Waits up to 15 minutes for that process to exit.
3. Sends `SIGKILL` if the process is still running.

Handle `SIGTERM` in your image if you need cleanup before exit. After a host stop, a new container instance may start on a different server when traffic needs it again.

Image updates during a deploy use the same stop sequence. Refer to [Rollouts](https://developers.cloudflare.com/containers/configuration/rollouts/).

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

```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/guides/outbound-traffic/) for how to control outbound traffic and internet access.

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/containers/faq/#page","headline":"Frequently Asked Questions · Cloudflare Containers docs","description":"Answers to common questions about Containers, including logging, scaling, cold starts, disk persistence, and rollouts.","url":"https://developers.cloudflare.com/containers/faq/","inLanguage":"en","image":"https://developers.cloudflare.com/og-docs.png","dateModified":"2026-08-28","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```
