diff --git a/src/content/changelog/containers/2026-09-30-durable-object-scheduling-policy.mdx b/src/content/changelog/containers/2026-09-30-durable-object-scheduling-policy.mdx
new file mode 100644
index 00000000000..ac287278b9d
--- /dev/null
+++ b/src/content/changelog/containers/2026-09-30-durable-object-scheduling-policy.mdx
@@ -0,0 +1,54 @@
+---
+title: New scheduling policy for Containers to configure image and instance from Durable Objects
+description: The durable_object scheduling policy gives each Durable Object control of Container configuration.
+products:
+ - containers
+date: "2026-09-30T08:45:00-04:00"
+publish_future_dated_entry: true
+---
+
+import { TypeScriptExample, WranglerConfig } from "~/components";
+
+[Containers](/containers/) now support the `durable_object` scheduling policy in public beta. This policy lets a Durable Object select the image and instance size for a Container at runtime instead of using one centrally managed configuration for the application.
+
+To use custom images, configure the policy and one or more named images in Wrangler:
+
+
+
+```jsonc
+{
+ "containers": [
+ {
+ "class_name": "AgentComputer",
+ "scheduling_policy": "durable_object",
+ "images": {
+ "base": {
+ "dockerfile": "./container/Dockerfile",
+ },
+ },
+ },
+ ],
+}
+```
+
+
+
+Wrangler prepares each image and exposes its immutable reference through `ctx.container.images`. Supply that reference and an instance size when you start the Container:
+
+
+
+```ts
+this.ctx.container.start({
+ image: this.ctx.container.images.base,
+ enableInternet: false,
+ instance: "standard-2",
+});
+```
+
+
+
+The `durable_object` policy also supports the new [`cloudflare/debian-trixie` Cloudflare-managed image](/containers/guides/image-management/#use-the-cloudflare-managed-image), which includes Node.js 24.20.0 on Debian Trixie slim. Start it directly without configuring a named image.
+
+Durable Object-managed Container instances have independent lifecycles and do not participate in application-wide image rollouts.
+
+For configuration, runtime sizing, snapshots, and update behavior, refer to [Scheduling Policies](/containers/configuration/scheduling-policy/).
diff --git a/src/content/changelog/containers/2026-09-30-snapshots.mdx b/src/content/changelog/containers/2026-09-30-snapshots.mdx
new file mode 100644
index 00000000000..bb62965c3fb
--- /dev/null
+++ b/src/content/changelog/containers/2026-09-30-snapshots.mdx
@@ -0,0 +1,47 @@
+---
+title: Snapshot and restore Container filesystem
+description: Persist point-in-time container filesystem with snapshot APIs in public beta.
+products:
+ - containers
+date: "2026-09-30T08:45:00-04:00"
+publish_future_dated_entry: true
+---
+
+import { TypeScriptExample } from "~/components";
+
+[Containers](/containers/) now support snapshot APIs in public beta for saving and restoring point-in-time filesystem state. Create a snapshot first, then pass it back to `start()` to restore files after container sleep, restart, or handoff to another Durable Object.
+
+Use `snapshotContainer()` through the [Durable Object Container API](/containers/api/durable-object-container/) to capture the full container filesystem. Create a snapshot from a running Container, store its handle, and pass that handle to `start()` when you restore it later:
+
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export class MyDurableObject extends DurableObject {
+ async saveSnapshot() {
+ // Create a snapshot from the running Container.
+ const containerSnapshot = await this.ctx.container.snapshotContainer({});
+
+ await this.ctx.storage.put("containerSnapshot", containerSnapshot);
+ }
+
+ async restoreSnapshot() {
+ // Restore the saved snapshot later.
+ const containerSnapshot =
+ await this.ctx.storage.get("containerSnapshot");
+
+ if (!containerSnapshot) {
+ return;
+ }
+
+ this.ctx.container.start({ containerSnapshot, enableInternet: false });
+ }
+}
+```
+
+
+
+Snapshots are only supported for Container applications that use the [`durable_object` scheduling policy](/containers/configuration/scheduling-policy/#use-the-durable-object-scheduling-policy). Snapshots are immutable, so create a new snapshot to persist filesystem changes made after a restore.
+
+For more information, refer to [Snapshots](/containers/guides/snapshots/) and the [Durable Object Container API](/containers/api/durable-object-container/).
diff --git a/src/content/docs/containers/api/container-class.mdx b/src/content/docs/containers/api/container-class.mdx
index 2d1cdfacf7c..9a1b81fdf9c 100644
--- a/src/content/docs/containers/api/container-class.mdx
+++ b/src/content/docs/containers/api/container-class.mdx
@@ -12,9 +12,17 @@ import { PackageManagers, TypeScriptExample } from "~/components";
The [`Container` class](https://github.com/cloudflare/containers) from [`@cloudflare/containers`](https://www.npmjs.com/package/@cloudflare/containers) provides lifecycle helpers for container instances. For direct lifecycle control, use the [Durable Object Container API](/containers/api/durable-object-container/).
+**`Container` extends [`DurableObject`](/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`](/durable-objects/api/sqlite-storage-api/) for persistent SQLite-backed storage and [`this.ctx.id`](/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.
+
+:::note
+The `Container` class does not support applications that use the [`durable_object` scheduling policy](/containers/configuration/scheduling-policy/#use-the-durable-object-scheduling-policy). It is available only with the `default` scheduling policy. For `durable_object` applications, use the [Durable Object Container API](/containers/api/durable-object-container/).
+:::
+
To move an existing application to direct control, refer to [Migrate to the Durable Object Container API](/containers/guides/migrate-to-durable-object-container-api/).
-**`Container` extends [`DurableObject`](/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`](/durable-objects/api/sqlite-storage-api/) for persistent SQLite-backed storage and [`this.ctx.id`](/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.
+## Use the Container class
+
+Start by installing the `@cloudflare/containers` package:
diff --git a/src/content/docs/containers/api/durable-object-container.mdx b/src/content/docs/containers/api/durable-object-container.mdx
index 5546b1c9a90..8f856aee758 100644
--- a/src/content/docs/containers/api/durable-object-container.mdx
+++ b/src/content/docs/containers/api/durable-object-container.mdx
@@ -9,7 +9,7 @@ products:
- durable-objects
---
-import { TypeScriptExample } from "~/components";
+import { Badge, TypeScriptExample } from "~/components";
Each [container](/containers/) is managed and proxied by a Durable Object. The Durable Object manages routing and persistent state. The container process runs your image inside a Linux VM.
@@ -35,7 +35,11 @@ export class MyDurableObject extends DurableObject {
throw new Error("No container is configured for this Durable Object");
}
if (!container.running) {
- container.start();
+ // With the `default` scheduling policy, call `container.start()` without options.
+ container.start({
+ image: container.images.base,
+ enableInternet: false,
+ });
await this.ctx.storage.put("lastStartedAt", Date.now());
}
}
@@ -54,6 +58,16 @@ export class MyDurableObject extends DurableObject {
## Attributes
+### `images`
+
+`images` is a read-only map generated from the [`images` field in the application's Wrangler configuration](/workers/wrangler/configuration/#durable_object-scheduling-policy). Wrangler builds or resolves each named image and exposes its digest-pinned reference under the same key. This attribute is available only for applications that use the [`durable_object` scheduling policy](/containers/configuration/scheduling-policy/#use-the-durable-object-scheduling-policy).
+
+```js
+const image = this.ctx.container.images.base;
+```
+
+Pass a value from this map as the `image` option to [`start()`](#start).
+
### `running`
`running` is `true` when the container is running. It does not confirm that the container is ready to accept requests.
@@ -68,21 +82,55 @@ this.ctx.container.running;
`start()` boots a container. It returns before the container is ready to accept requests. Confirm readiness before sending traffic.
+The required options depend on the application's scheduling policy.
+
```js
+// `default` scheduling policy:
+// The image and instance size come from Wrangler configuration.
this.ctx.container.start();
+
+// `durable_object` scheduling policy:
+// Pass an image and choose whether to allow outbound Internet access.
+// The instance size is optional and defaults to "lite".
+this.ctx.container.start({
+ image: this.ctx.container.images.base,
+ enableInternet: true,
+});
```
#### Parameters
-- `options` (`object`, optional): Common container startup options:
+- `options` (`object`, conditionally required): Startup options. Required for applications that use the `durable_object` scheduling policy and optional for applications that use the `default` scheduling policy.
- `env` (`Record`, optional): Environment variables to pass to the container.
- `entrypoint` (`string[]`, optional): Command and arguments to run in the container.
- `enableInternet` (`boolean`, required): Whether to allow outbound Internet access. Required when you pass `options`.
+ - `image` (`string`, conditionally required) : Image reference to start. Required unless you pass `containerSnapshot`. Pass the [`cloudflare/debian-trixie` managed image](/containers/guides/image-management/#use-the-cloudflare-managed-image) or a value from [`ctx.container.images`](#images).
+ - `instance` (`string | object`, optional) : Instance size. Pass `"lite"`, `"standard-1"`, `"standard-2"`, `"standard-3"`, `"standard-4"`, or a custom object with `vcpu`, `memoryMib`, and `diskMb` properties. Defaults to `"lite"`.
+ - `containerSnapshot` (`ContainerSnapshotRestoreParams`, optional) : Snapshot handle to restore before startup. Pass the `ContainerSnapshot` returned by [`snapshotContainer()`](#snapshotcontainer), or an object containing its `id`. You cannot pass both `containerSnapshot` and `image`.
+ - `labels` (`Record`, optional): Up to 10 labels that [`inspect()`](#inspect) returns. Label names must contain 1 to 16 bytes. Label values can contain up to 64 bytes. Names and values cannot contain control characters.
#### Return values
- `void`: No return value.
+### `inspect`
+
+`inspect()` returns the image and labels for a running container. It returns `null` when no container is running.
+
+```js
+const containerInfo = await this.ctx.container.inspect();
+```
+
+#### Parameters
+
+- None.
+
+#### Return values
+
+- `Promise`: Resolves with `null` when no container is running. Otherwise, it resolves with a `ContainerInfo` object containing:
+ - `image` (`string`): Image reference passed to [`start()`](#start). The value is an empty string while the container is starting and for a container restored from `containerSnapshot`.
+ - `labels` (`Record`): Labels passed to [`start()`](#start).
+
### `exec`
`exec()` starts another process inside an already-running container. It does not start a stopped container.
@@ -170,6 +218,30 @@ With `stderr: "combined"`, `stderr` is `null` on `ExecProcess` and an empty `Arr
For task-oriented examples, refer to [Execute commands](/containers/guides/execute-commands/).
+### `snapshotContainer`
+
+`snapshotContainer()` creates a point-in-time snapshot of the running container filesystem. This method is only supported for applications that use the [`durable_object` scheduling policy](/containers/configuration/scheduling-policy/#use-the-durable-object-scheduling-policy).
+
+```js
+const snapshot = await this.ctx.container.snapshotContainer({
+ name: "before-upgrade",
+});
+```
+
+#### Parameters
+
+- `options` (`ContainerSnapshotOptions`): Snapshot configuration:
+ - `name` (`string`, optional): Human-readable name for the snapshot.
+
+#### Return values
+
+- `Promise`: Resolves with an opaque handle for the stored filesystem snapshot. The snapshot data is not returned to the Worker. The handle contains:
+ - `id` (`string`): Unique snapshot identifier. Pass the returned `ContainerSnapshot` to [`start()`](#start) as `containerSnapshot`, or store it for a later restore.
+ - `size` (`number`): Snapshot size in bytes.
+ - `name` (`string`, optional): Human-readable name supplied in `options`.
+
+Container snapshots are immutable. Snapshot handles have an implicit 30-day time-to-live that refreshes when you restore them. For the complete save and restore flow, refer to [Use snapshots](/containers/guides/snapshots/).
+
### `destroy`
`destroy()` stops the container and can include an optional reason for the operation.
@@ -272,7 +344,11 @@ interface Env {}
class MyDurableObject extends DurableObject {
startAndMonitor() {
const container = this.ctx.container;
- container.start();
+ // With the `default` scheduling policy, call `container.start()` without options.
+ container.start({
+ image: container.images.base,
+ enableInternet: false,
+ });
this.ctx.waitUntil(
container
.monitor()
@@ -374,3 +450,4 @@ await this.ctx.container.interceptOutboundHttps("*", worker);
- [Get started with Containers](/containers/get-started/): Deploy your first container.
- [SQLite storage API](/durable-objects/api/sqlite-storage-api/): Persist state across container restarts.
- [Durable Objects](/durable-objects/): The underlying platform that powers Containers.
+- [Snapshots](/containers/guides/snapshots/): Save and restore container filesystems.
diff --git a/src/content/docs/containers/concepts/architecture.mdx b/src/content/docs/containers/concepts/architecture.mdx
index dbdd81beb46..3ac7bdf3d11 100644
--- a/src/content/docs/containers/concepts/architecture.mdx
+++ b/src/content/docs/containers/concepts/architecture.mdx
@@ -10,12 +10,13 @@ products:
## Deployment
-After you deploy an application with a Container, your image is uploaded to
-[Cloudflare's Registry](/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.
+How images and running Container instances update depends on the [scheduling policy](/containers/configuration/scheduling-policy/) for the application.
-Worker code goes live on deploy. Container instances update with a [rollout](/containers/configuration/rollouts/). Refer to [Deploy Containers](/containers/guides/deploy/).
+With the `durable_object` policy, Wrangler prepares the named images for the application. Durable Object code can access their immutable references and selects an image and instance size when it calls `ctx.container.start()`. When an image reference changes, the Durable Object code decides whether to stop the running Container and start it with the new image or let it continue with the previous image. This application-controlled restart is how you roll out image changes with the `durable_object` policy. These instances do not participate in application-wide image rollouts.
+
+With the `default` policy, Wrangler uploads or resolves the application image. Cloudflare distributes that image across its network and prepares capacity for new instances. Changes to the image or instance type use a [rollout](/containers/configuration/rollouts/).
+
+Worker code goes live on deploy before any application-wide Container rollout finishes. Refer to [Deploy Containers](/containers/guides/deploy/).
## Container instance lifecycle
@@ -70,8 +71,7 @@ developers to address and route to specific container instances, run code when a
### 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.
+When a Durable Object requests a new Container instance, Cloudflare selects eligible capacity with the required image available. The `durable_object` policy uses the `image` and `instance` options supplied to `ctx.container.start()`. The `default` policy uses the application image and instance type from Wrangler configuration.
:::note
Durable Objects and their associated Container instances are not guaranteed to run in the
@@ -81,12 +81,7 @@ Container placement is optimized for request routing and startup speed, so a Con
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.
+Starting additional Container instances can use other locations where the image is available. Cloudflare prepares additional capacity as demand grows. Because prepared capacity is finite, some Container instances may start in locations farther from the end user. You are only charged for actively running instances, not for prepared images that are not running.
#### Cold starts
@@ -145,13 +140,14 @@ The [`Container` class](/containers/api/container-class/) adds hooks that run Wo
Refer to the [status hooks example](/containers/examples/status-hooks/) for a full implementation.
-#### Persistent disk
+#### Use snapshots
-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.
+All disk is ephemeral by default. When a Container instance goes to sleep, the
+next time it starts, it uses a fresh disk from the container image.
-Snapshots are coming soon, which allow the user to quickly persist and restore the disk
-from an entire container or a directory.
+If you need point-in-time filesystem state, Container applications that use the [`durable_object` scheduling policy](/containers/configuration/scheduling-policy/#use-the-durable-object-scheduling-policy) can create and restore a snapshot.
+Snapshots are immutable, so later file changes require a new snapshot. For more
+information, refer to [Snapshots](/containers/guides/snapshots/).
You can also use [FUSE](/containers/examples/r2-fuse-mount/) to persist disk
to R2 or other object storage backends. Though you should not expect native
diff --git a/src/content/docs/containers/configuration/index.mdx b/src/content/docs/containers/configuration/index.mdx
index 787c7a1766a..1426373687f 100644
--- a/src/content/docs/containers/configuration/index.mdx
+++ b/src/content/docs/containers/configuration/index.mdx
@@ -1,7 +1,7 @@
---
pcx_content_type: navigation
title: Configuration
-description: Configure Containers in Wrangler, connect them to Workers and bindings, set environment variables, tune scaling and routing, and manage rollouts.
+description: Choose a scheduling policy, configure Containers in Wrangler, connect bindings, set environment variables, tune scaling, and manage rollouts.
sidebar:
order: 5
group:
diff --git a/src/content/docs/containers/configuration/rollouts.mdx b/src/content/docs/containers/configuration/rollouts.mdx
index cb9b73c551c..b6552166590 100644
--- a/src/content/docs/containers/configuration/rollouts.mdx
+++ b/src/content/docs/containers/configuration/rollouts.mdx
@@ -10,6 +10,10 @@ products:
import { WranglerConfig, PackageManagers } from "~/components";
+:::note
+Rollouts apply to Container applications that use the [`default` scheduling policy](/containers/configuration/scheduling-policy/). Durable Object-managed Container instances do not participate in application-wide rollouts; application code selects their image when it calls `ctx.container.start()`.
+:::
+
## How rollouts work
A **rollout** applies a target container application configuration after you [deploy](/containers/guides/deploy/) a Worker that uses Containers. The target can change the image, instance type, limits, placement, or other container settings.
@@ -144,12 +148,12 @@ Use none when the deploy should not publish a new image or start a container ins
},
],
},
- "migrations": [
- {
- "tag": "v1",
- "new_sqlite_classes": ["MyContainer"],
+ "exports": {
+ "MyContainer": {
+ "type": "durable-object",
+ "storage": "sqlite",
},
- ],
+ },
}
```
diff --git a/src/content/docs/containers/configuration/scheduling-policy.mdx b/src/content/docs/containers/configuration/scheduling-policy.mdx
new file mode 100644
index 00000000000..9cf6f1baf83
--- /dev/null
+++ b/src/content/docs/containers/configuration/scheduling-policy.mdx
@@ -0,0 +1,202 @@
+---
+pcx_content_type: concept
+title: Scheduling Policies
+description: Choose whether Container configuration is managed centrally or by each Durable Object at runtime.
+sidebar:
+ order: 1
+products:
+ - containers
+---
+
+import { TypeScriptExample, WranglerConfig } from "~/components";
+
+A scheduling policy determines where you configure a Container image and [instance size](/containers/platform/limits/#instance-types). It also determines how image updates and [rollouts](/containers/configuration/rollouts/) apply. Choose the policy when you create the Container application.
+
+| Policy | Configure image and instance size | Image updates | Best for |
+| ----------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
+| `default` | In Wrangler configuration | Cloudflare applies application-wide configuration changes with [rollouts](/containers/configuration/rollouts/) | Services whose instances use the same image and instance size |
+| `durable_object` (beta) | In Durable Object code when calling `ctx.container.start()` | Application code selects an image each time it starts an instance | Sandboxes, agent environments, and other workloads that need per-instance configuration |
+
+:::note
+The `durable_object` scheduling policy is in public beta.
+:::
+
+One Wrangler configuration can contain applications with both policies. This lets a Worker use centrally managed service Containers alongside Durable Object-managed sandboxes.
+
+The scheduling policy is immutable. To use a different policy, create a new Container application. Deleting an application also deletes its Container instances, so switching policies means replacing every running instance. To move an existing application, refer to [Migrate to the Durable Object scheduling policy](/containers/guides/migrate-to-durable-object-scheduling-policy/).
+
+## Use the default scheduling policy
+
+With the `default` policy, Wrangler configuration defines one application-wide `image`, one [`instance_type`](/containers/platform/limits/#instance-types), and settings such as `max_instances`. Omitting `scheduling_policy` selects `default`.
+
+
+
+```jsonc
+{
+ "containers": [
+ {
+ "class_name": "ApiContainer",
+ "scheduling_policy": "default",
+ "image": "./api/Dockerfile",
+ "instance_type": "standard-1",
+ "max_instances": 5,
+ },
+ ],
+}
+```
+
+
+
+When you change the image or instance type and deploy, Cloudflare rolls out that change across the application. Refer to [Rollouts](/containers/configuration/rollouts/).
+
+## Use the Durable Object scheduling policy
+
+The `durable_object` policy moves per-instance decisions into your Durable Object. Wrangler associates the Container application with the Durable Object class, and your code supplies a startup image or snapshot and an optional instance size to `ctx.container.start()`.
+
+Configure the policy. To use custom images, add the images that the Durable Object can start:
+
+
+
+```jsonc
+{
+ "name": "agent-computer",
+ "main": "src/index.ts",
+ "compatibility_date": "2026-09-29",
+ "containers": [
+ {
+ "class_name": "AgentComputer",
+ "scheduling_policy": "durable_object",
+ "images": {
+ "base": {
+ "dockerfile": "./container/Dockerfile",
+ },
+ },
+ },
+ ],
+ "durable_objects": {
+ "bindings": [
+ {
+ "name": "AGENT_COMPUTER",
+ "class_name": "AgentComputer",
+ },
+ ],
+ },
+ "exports": {
+ "AgentComputer": {
+ "type": "durable-object",
+ "storage": "sqlite",
+ },
+ },
+}
+```
+
+
+
+Wrangler builds or resolves each named image, prepares it for the Containers runtime, and exposes its digest-pinned reference on `ctx.container.images`. Select an image and instance size when the Durable Object starts its Container:
+
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export class AgentComputer extends DurableObject {
+ startContainer() {
+ if (this.ctx.container.running) {
+ return;
+ }
+
+ this.ctx.container.start({
+ image: this.ctx.container.images.base,
+ instance: "standard-2",
+ enableInternet: false,
+ });
+ }
+}
+```
+
+
+
+`ctx.container.start()` initiates startup and returns before the Container is ready to accept requests. Add an application-specific readiness check before sending traffic or calling [`exec()`](/containers/api/durable-object-container/#exec).
+
+The `durable_object` policy does not support `max_instances`. Running instances count toward your [account limits](/containers/platform/limits/#account-limits).
+
+### Use the Cloudflare-managed image
+
+If you do not need a custom image, start the [`cloudflare/debian-trixie` Cloudflare-managed image](/containers/guides/image-management/#use-the-cloudflare-managed-image) directly without adding a named image to Wrangler. It includes Node.js 24.20.0 on Debian Trixie slim:
+
+
+
+```ts
+this.ctx.container.start({
+ image: "cloudflare/debian-trixie",
+ instance: "standard-2",
+ enableInternet: false,
+});
+```
+
+
+
+### Configure named images
+
+Each key under `containers[].images` in your Wrangler configuration becomes a property on `ctx.container.images`. For example, `containers[].images.base` is available to the Durable Object as `ctx.container.images.base`. Each value must specify exactly one image source. Use `dockerfile` for a path to a Dockerfile. When you run `wrangler deploy`, Wrangler builds and uploads that image. You can also set `build_context` and `build_vars` for that image.
+
+Use `image` for a digest-pinned image in the Cloudflare managed registry. Use the form `registry.cloudflare.com//@sha256:`. Direct references to Docker Hub, Amazon ECR, or Google Artifact Registry are not supported. To use an image from another registry, [push it to the Cloudflare managed registry](/containers/guides/image-management/#use-an-external-image) first.
+
+A configuration can contain up to 100 named images. An image name must contain between 1 and 128 characters.
+
+The image map is uploaded with the Worker version. Updating the map does not restart or replace running Container instances. Your application selects an image from the map when it starts an instance. During a [gradual deployment](/workers/versions-and-deployments/gradual-deployments/), `ctx.container.images` reflects the image map of the Worker version that runs the Durable Object, so different Durable Objects can start different images until the deployment completes.
+
+### Choose an instance size at runtime
+
+Set `instance` in `ctx.container.start()` to one of the following [named instance types](/containers/platform/limits/#instance-types):
+
+- `lite`
+- `standard-1`
+- `standard-2`
+- `standard-3`
+- `standard-4`
+
+The runtime does not accept `basic` or the legacy `dev` and `standard` aliases.
+
+If you omit `instance`, the Container uses `lite`. You can also supply a custom instance object that meets the [custom instance type constraints](/containers/platform/limits/#custom-instance-types):
+
+
+
+```ts
+this.ctx.container.start({
+ image: this.ctx.container.images.base,
+ enableInternet: false,
+ instance: {
+ vcpu: 1,
+ memoryMib: 4096,
+ diskMb: 8000,
+ },
+});
+```
+
+
+
+The runtime uses camel case (`memoryMib` and `diskMb`). Wrangler's application-level `instance_type` object uses snake case (`memory_mib` and `disk_mb`) and only applies to the `default` policy.
+
+### Start from an image or snapshot
+
+For a new filesystem, pass `image` to `ctx.container.start()`. To restore a full filesystem snapshot, pass `containerSnapshot` instead. `image` and `containerSnapshot` are mutually exclusive because a snapshot already identifies the filesystem to restore.
+
+Snapshots are only supported with the `durable_object` policy. A snapshot is tied to the image it was created from. After you update an image, create new snapshots from Containers running that image.
+
+Refer to [Snapshots](/containers/guides/snapshots/) for the complete save and restore flow.
+
+### Manage updates from application code
+
+Container instances that use the `durable_object` policy do not participate in application-wide image rollouts. A running instance continues to use its startup image. Your application decides when to stop that instance and start it with another configured image. For an example that compares the running image with the configured image, refer to [Roll out a named image update](/containers/guides/image-management/#roll-out-a-named-image-update).
+
+### Configure SSH
+
+The `durable_object` policy supports the `ssh` and `authorized_keys` fields. Refer to [SSH](/containers/guides/ssh/).
+
+## Related resources
+
+- [Durable Object Container API](/containers/api/durable-object-container/)
+- [Lifecycle of a Container](/containers/concepts/architecture/)
+- [Image Management](/containers/guides/image-management/)
+- [Wrangler Containers configuration](/workers/wrangler/configuration/#containers)
diff --git a/src/content/docs/containers/faq.mdx b/src/content/docs/containers/faq.mdx
index 94ddc8be3f1..edc78700af3 100644
--- a/src/content/docs/containers/faq.mdx
+++ b/src/content/docs/containers/faq.mdx
@@ -104,11 +104,12 @@ Refer to [image management](/containers/guides/image-management/#use-pre-built-c
## 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.
+All disk is ephemeral by default. When a Container instance goes to sleep, the
+next time it starts, it uses a fresh disk from the container image.
-Snapshots are coming soon, which allow the user to quickly persist and restore the disk
-from an entire container or a directory.
+If you need point-in-time filesystem state, Container applications that use the [`durable_object` scheduling policy](/containers/configuration/scheduling-policy/#use-the-durable-object-scheduling-policy) can create and restore a snapshot.
+Snapshots are immutable, so later file changes require a new snapshot. For more
+information, refer to [Snapshots](/containers/guides/snapshots/).
You can also use [FUSE](/containers/examples/r2-fuse-mount/) to persist disk
to R2 or other object storage backends. Though you should not expect native
diff --git a/src/content/docs/containers/get-started/index.mdx b/src/content/docs/containers/get-started/index.mdx
index 1c9b1dac719..61ba08564bb 100644
--- a/src/content/docs/containers/get-started/index.mdx
+++ b/src/content/docs/containers/get-started/index.mdx
@@ -173,7 +173,7 @@ This defines basic configuration for the container:
The `Container` class itself extends [`DurableObject`](/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`](/durable-objects/api/sqlite-storage-api/) to persist data that survives container restarts and resides close to the container itself.
-For all properties and methods, refer to the [Container class API](/containers/api/container-class/).
+For all properties and methods, refer to the [Container class API](/containers/api/container-class/) and the [Durable Object Container API](/containers/api/durable-object-container/).
#### Routing to Containers
diff --git a/src/content/docs/containers/guides/image-management.mdx b/src/content/docs/containers/guides/image-management.mdx
index dff2c55fdca..04aa7a6f6a0 100644
--- a/src/content/docs/containers/guides/image-management.mdx
+++ b/src/content/docs/containers/guides/image-management.mdx
@@ -1,16 +1,163 @@
---
pcx_content_type: how-to
title: Image Management
-description: Learn how to use Cloudflare Registry, Docker Hub, and Amazon ECR images with Containers.
+description: Configure and update Container images with the durable_object and default scheduling policies.
sidebar:
order: 4
products:
- containers
---
-import { WranglerConfig, PackageManagers, Steps } from "~/components";
+import {
+ PackageManagers,
+ Steps,
+ TypeScriptExample,
+ WranglerConfig,
+} from "~/components";
-## Push images during `wrangler deploy`
+Container applications manage images differently based on their [scheduling policy](/containers/configuration/scheduling-policy/). With the `durable_object` policy, Durable Object code selects an image each time it starts a Container. With the `default` policy, Wrangler configuration sets one image for the application.
+
+## Use images with the `durable_object` scheduling policy
+
+### Use the Cloudflare-managed image
+
+The `cloudflare/debian-trixie` managed image is available only with the `durable_object` scheduling policy:
+
+| Managed image | Source image | Contents |
+| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
+| `cloudflare/debian-trixie` | [`node:24.20.0-trixie-slim`](https://hub.docker.com/layers/library/node/24.20.0-trixie-slim/images/sha256-a747ad80c8a161b650d79a6da9c422005b91148b18b8d2c669eb5a0b7c07e600) pinned by digest | Node.js 24.20.0 on Debian Trixie slim |
+
+`cloudflare/debian-trixie` is a Cloudflare-managed identifier, not a public Cloudflare Docker Hub image. Pass the identifier when the Durable Object starts its Container:
+
+
+
+```ts
+this.ctx.container.start({
+ image: "cloudflare/debian-trixie",
+ enableInternet: false,
+});
+```
+
+
+
+### Configure named images
+
+Configure one or more named images in the `images` map. Each entry must use exactly one image source. Set `dockerfile` to build an image from a local Dockerfile. Set `image` to use a digest-pinned image from the Cloudflare managed registry.
+
+The following configuration defines one image from each source:
+
+
+
+```jsonc
+{
+ "containers": [
+ {
+ "class_name": "AgentComputer",
+ "scheduling_policy": "durable_object",
+ "images": {
+ "base": {
+ "dockerfile": "./container/Dockerfile",
+ "build_context": ".",
+ },
+ "tools": {
+ "image": "registry.cloudflare.com//@sha256:",
+ },
+ },
+ },
+ ],
+}
+```
+
+
+
+Wrangler builds or resolves each named image and exposes its digest-pinned reference through `ctx.container.images`. Select a reference when the Durable Object starts its Container:
+
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export class AgentComputer extends DurableObject {
+ startContainer() {
+ this.ctx.container.start({
+ image: this.ctx.container.images.base,
+ instance: "standard-2",
+ enableInternet: false,
+ });
+ }
+}
+```
+
+
+
+A configuration can contain up to 100 named images. Each image name must contain between 1 and 128 characters. For the complete policy configuration, refer to [Scheduling Policies](/containers/configuration/scheduling-policy/#use-the-durable-object-scheduling-policy).
+
+### Roll out a named image update
+
+To update a named image, change its Dockerfile or set `image` to a new digest-pinned reference. Then, deploy the Worker. After the deployment completes, the corresponding value in `ctx.container.images` refers to the updated image. During a [Workers gradual deployment](/workers/versions-and-deployments/gradual-deployments/), each Durable Object sees the image map of the Worker version that runs it, so different Durable Objects can start different images until the deployment completes.
+
+Deploying an updated image map does not restart running Containers. A running Container continues to use its startup image. Durable Object code decides when to stop that Container and start it with the updated reference. This application-controlled restart is how you roll out image changes with the `durable_object` policy.
+
+To keep a running Container on its current image, use the configured image only on its next start:
+
+
+
+```ts
+if (!this.ctx.container.running) {
+ this.ctx.container.start({
+ image: this.ctx.container.images.base,
+ instance: "standard-2",
+ enableInternet: false,
+ });
+}
+```
+
+
+
+To upgrade a running Container immediately, compare its image with the configured image. When they differ, stop the Container and start it with the configured image.
+
+`inspect()` reports an empty image while a Container is starting and for a Container restored from a snapshot. The following example does not replace a Container in either case:
+
+
+
+```ts
+const image = this.ctx.container.images.base;
+const info = await this.ctx.container.inspect();
+
+if (info && info.image !== "" && info.image !== image) {
+ await this.ctx.container.destroy();
+}
+
+if (!this.ctx.container.running) {
+ this.ctx.container.start({
+ image,
+ instance: "standard-2",
+ enableInternet: false,
+ });
+}
+```
+
+
+
+:::caution
+`destroy()` stops the Container immediately. In-progress work and any filesystem changes made since startup are lost. To preserve the filesystem, create a [snapshot](/containers/guides/snapshots/) before you stop the Container. A snapshot is tied to the image it was created from, so you cannot restore it onto the updated image.
+:::
+
+### Use an external image
+
+:::note
+The `durable_object` scheduling policy does not support direct image references from external registries. A named `image` source must use a digest-pinned reference from the Cloudflare managed registry.
+:::
+
+To use an image from another registry, pull it and [push it to the Cloudflare managed registry](#use-images-from-other-registries). Then, [get its digest](#get-the-digest-of-a-pushed-image) and configure the resulting digest-pinned reference as a named image.
+
+A named `dockerfile` entry can use an external image in its `FROM` instruction. Authenticate your local Docker installation before deploying if the base image is private.
+
+## Use images with the `default` scheduling policy
+
+Set the singular `image` field to a local Dockerfile or a supported registry reference. Changing this field deploys the new application image through an [application-wide rollout](/containers/configuration/rollouts/).
+
+### Push images during `wrangler deploy`
When running `wrangler deploy`, if you set the `image` attribute in your [Wrangler configuration](/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](/r2/). All authentication is handled automatically by
@@ -41,9 +188,9 @@ Docker or a Docker-compatible CLI tool must be running for Wrangler to build and
This is not necessary if you are using a pre-built image, as described below.
:::
-## Use pre-built container images
+### 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).
+With the `default` scheduling policy, 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.
@@ -51,7 +198,11 @@ Cloudflare does not cache images pulled from Docker Hub, Amazon ECR, or Google A
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
+:::note
+The `durable_object` scheduling policy does not pull from Docker Hub, Amazon ECR, or Google Artifact Registry, and does not use registry credentials configured with `wrangler containers registries configure`. Refer to [Use an external image](#use-an-external-image).
+:::
+
+#### 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.
@@ -81,7 +232,7 @@ If Docker Hub credentials have been configured, those credentials are used to pu
Official Docker Hub images use the `library` namespace. For example, use `docker.io/library/:` instead of `docker.io/:`.
:::
-### Configure private registry credentials
+#### Configure private registry credentials
To use a private image from Docker Hub, Amazon ECR, or Google Artifact Registry, run [`wrangler containers registries configure`](/workers/wrangler/commands/containers/#containers-registries-configure) for the registry domain.
@@ -89,7 +240,7 @@ Wrangler prompts for the secret and stores it in [Secrets Store](/secrets-store)
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
+#### Use private Docker Hub images
Configure Docker Hub in Wrangler using these values:
@@ -122,7 +273,7 @@ printf '%s' "$DOCKERHUB_PAT" | npx wrangler containers registries configure dock
After you configure the registry, use the same fully qualified Docker Hub image reference shown above.
-### Use private Amazon ECR images
+#### Use private Amazon ECR images
Configure Amazon ECR in Wrangler using these values:
@@ -191,7 +342,7 @@ After you configure the registry, use the fully qualified Amazon ECR image refer
-### Use private Google Artifact Registry images
+#### Use private Google Artifact Registry images
Configure Google Artifact Registry in Wrangler using these values:
@@ -252,6 +403,20 @@ image = "-docker.pkg.dev///:"
+To use an image from a registry not listed above, [push it to the Cloudflare Registry](#use-images-from-other-registries).
+
+:::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 to the Cloudflare Registry
+
+Both scheduling policies can use images that you push to the Cloudflare Registry yourself. With the `durable_object` policy, this is required for any image from Docker Hub, Amazon ECR, Google Artifact Registry, or another external registry.
+
### 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:
@@ -277,7 +442,7 @@ Or, you can use the `-p` flag with `wrangler containers build` to build and push
args="containers build -p -t ."
/>
-This will output an image registry URI that you can then use in your Wrangler configuration:
+This will output an image registry URI. With the `default` scheduling policy, you can use this tag reference in your Wrangler configuration:
@@ -293,19 +458,43 @@ This will output an image registry URI that you can then use in your Wrangler co
-:::note
-With `wrangler dev`, image references from the Cloudflare Registry, Docker Hub, Amazon ECR, and Google Artifact Registry are supported in local development.
+With the `durable_object` scheduling policy, a named `image` source requires a digest-pinned reference. [Get the digest of the pushed image](#get-the-digest-of-a-pushed-image) first.
-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.
+### Get the digest of a pushed image
-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`.
-:::
+After you push an image, list its repository digests with Docker:
+
+```bash
+docker image inspect --format '{{join .RepoDigests "\n"}}' registry.cloudflare.com//:
+```
+
+Use the entry that starts with `registry.cloudflare.com/` as a named `image` source:
+
+
+
+```jsonc
+{
+ "containers": [
+ {
+ "class_name": "AgentComputer",
+ "scheduling_policy": "durable_object",
+ "images": {
+ "tools": {
+ "image": "registry.cloudflare.com//@sha256:",
+ },
+ },
+ },
+ ],
+}
+```
+
+
-## Push images with CI
+### 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.
+using the `wrangler containers push` command. With the `durable_object` scheduling policy, [get the digest of the pushed image](#get-the-digest-of-a-pushed-image) before you update your Wrangler configuration.
## Registry limits
diff --git a/src/content/docs/containers/guides/migrate-to-durable-object-scheduling-policy.mdx b/src/content/docs/containers/guides/migrate-to-durable-object-scheduling-policy.mdx
new file mode 100644
index 00000000000..c88683478d8
--- /dev/null
+++ b/src/content/docs/containers/guides/migrate-to-durable-object-scheduling-policy.mdx
@@ -0,0 +1,226 @@
+---
+pcx_content_type: how-to
+title: Migrate to the Durable Object scheduling policy
+description: Replace a Container application that uses the default scheduling policy with a Durable Object-managed application.
+sidebar:
+ order: 8
+products:
+ - containers
+---
+
+import {
+ PackageManagers,
+ Steps,
+ TypeScriptExample,
+ WranglerConfig,
+} from "~/components";
+
+You cannot change the scheduling policy of an existing Container application. To move from the `default` policy to the `durable_object` policy, create a replacement Container application and cut traffic over to it.
+
+The replacement application must use a new Durable Object class and namespace. The old and replacement applications cannot attach to the same Durable Object namespace.
+
+:::caution
+
+This process does not transfer existing Container instances or Durable Object storage. The `default` scheduling policy does not support snapshots, so you cannot use a Container snapshot to transfer a filesystem.
+
+If the existing Durable Objects contain data, design an application-specific transfer process before continuing.
+
+:::
+
+## Before you begin
+
+- **Replace the `Container` class.** The [`Container` class](/containers/api/container-class/) does not support the `durable_object` policy. If your existing class extends `Container`, the replacement class must use the [Durable Object Container API](/containers/api/durable-object-container/) directly. Rebuild any `Container` class helpers your application relies on, such as port readiness checks, request proxying, and sleep timeouts. Refer to [Migrate to the Durable Object Container API](/containers/guides/migrate-to-durable-object-container-api/).
+- **Check how the Worker declares Durable Object classes.** A Worker uses either the [`exports` field](/durable-objects/reference/durable-objects-migrations/) or the [legacy `migrations` array](/durable-objects/reference/durable-object-class-migrations-legacy/), not both. Declare the replacement class with the same mechanism the Worker already uses. Moving from `migrations` to `exports` cannot be undone. If you want to move, do it as a separate change. Refer to [Migrate from the legacy `migrations` flow](/durable-objects/reference/durable-objects-migrations/#migrate-from-the-legacy-migrations-flow).
+- **Move external images to the Cloudflare managed registry.** Named images with the `durable_object` policy must use a Dockerfile or a digest-pinned reference in the Cloudflare managed registry. If the existing `image` references Docker Hub, Amazon ECR, or Google Artifact Registry, [push the image to the Cloudflare managed registry](/containers/guides/image-management/#use-an-external-image) first.
+
+## Configuration changes
+
+Map the existing application configuration to its `durable_object` equivalent:
+
+| `default` policy | `durable_object` policy |
+| ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `image` (Dockerfile path) | `dockerfile` on a named `images` entry, selected with `ctx.container.start()` |
+| `image` (registry reference) | `image` on a named `images` entry. Must be a digest-pinned reference in the Cloudflare managed registry |
+| `image_build_context` | `build_context` on the named image |
+| `image_vars` | `build_vars` on the named image |
+| `instance_type` | The `instance` option in `ctx.container.start()`. Refer to [Instance size changes](#instance-size-changes) |
+| `max_instances` | Not supported. Running instances count toward [account limits](/containers/platform/limits/#account-limits). Enforce any per-application cap in application code |
+| Application-wide image rollouts | Application code that stops and starts each Container |
+| `observability` | Keep in Wrangler configuration, subject to `durable_object` restrictions |
+| `unsafe.configuration.experimental_flags` | Keep in Wrangler configuration |
+| `ssh` and `authorized_keys` | Keep in Wrangler configuration |
+| Placement constraints, rollout settings, `wrangler_ssh`, and `trusted_user_ca_keys` | Not supported on the replacement application |
+
+For the complete field compatibility list, refer to [Wrangler configuration](/workers/wrangler/configuration/#containers).
+
+### Instance size changes
+
+With the `durable_object` policy, you set the instance size with the `instance` option of `ctx.container.start()` instead of `instance_type` in Wrangler configuration. `instance` accepts `lite`, `standard-1`, `standard-2`, `standard-3`, and `standard-4`. If your existing application uses one of the following `instance_type` values, choose a replacement:
+
+- `dev`: Use `lite`.
+- `standard`: Use `standard-1`.
+- `basic`: Choose `lite` or `standard-1`. Custom instance types require at least 1 vCPU, so you cannot reproduce `basic` with a custom instance.
+
+Custom instance objects use camel case at runtime. Rename `memory_mib` to `memoryMib` and `disk_mb` to `diskMb`. Refer to [Choose an instance size at runtime](/containers/configuration/scheduling-policy/#choose-an-instance-size-at-runtime).
+
+## Move the application
+
+
+
+1. **Add a new Durable Object class and Container application.**
+
+ You cannot move the existing application by changing its `scheduling_policy` to `durable_object`, because the scheduling policy cannot be changed after creation. You also cannot reuse the existing Durable Object class, because each Durable Object namespace attaches to one Container application.
+
+ :::caution
+ Do not change `scheduling_policy` on the existing Container entry. `wrangler deploy` deploys the new Worker version before it configures the Container application. The deploy then fails with the new Worker code already live and no `durable_object` application behind it.
+ :::
+
+ Instead, add all of the following to the Wrangler configuration:
+
+ - A new SQLite-backed Durable Object class.
+ - A Durable Object binding for the new class.
+ - A new Container entry with `"scheduling_policy": "durable_object"`, a different `name`, and `class_name` set to the new class.
+
+ Leave the existing Container entry, Durable Object class, and binding unchanged so that you can route traffic back to them. Changing the existing entry's `name` or `class_name` can cause Wrangler to create a new application instead of updating the existing one.
+
+ The following example uses `exports`. If the Worker uses the legacy `migrations` array, add a new migration with `new_sqlite_classes: ["DurableSandbox"]` instead.
+
+
+
+ ```jsonc
+ {
+ "name": "sandbox-worker",
+ "main": "src/index.ts",
+ "compatibility_date": "2026-09-29",
+ "containers": [
+ // Existing application. Keep this entry unchanged.
+ {
+ "class_name": "Sandbox",
+ "image": "./container/Dockerfile",
+ "instance_type": "standard-2",
+ "max_instances": 10,
+ },
+ // Replacement application.
+ {
+ "name": "sandbox-durable-object",
+ "class_name": "DurableSandbox",
+ "scheduling_policy": "durable_object",
+ "images": {
+ "base": {
+ "dockerfile": "./container/Dockerfile",
+ },
+ },
+ },
+ ],
+ "durable_objects": {
+ "bindings": [
+ {
+ "name": "SANDBOX",
+ "class_name": "Sandbox",
+ },
+ {
+ "name": "DURABLE_SANDBOX",
+ "class_name": "DurableSandbox",
+ },
+ ],
+ },
+ "exports": {
+ "Sandbox": {
+ "type": "durable-object",
+ "storage": "sqlite",
+ },
+ "DurableSandbox": {
+ "type": "durable-object",
+ "storage": "sqlite",
+ },
+ },
+ }
+ ```
+
+
+
+2. **Move startup configuration into the replacement class.**
+
+ Select the image and instance size when the replacement Durable Object starts its Container:
+
+
+
+ ```ts
+ import { DurableObject } from "cloudflare:workers";
+
+ export class DurableSandbox extends DurableObject {
+ startContainer() {
+ if (this.ctx.container.running) {
+ return;
+ }
+
+ this.ctx.container.start({
+ image: this.ctx.container.images.base,
+ instance: "standard-2",
+ // Match the existing application's outbound access.
+ enableInternet: true,
+ });
+ }
+ }
+ ```
+
+
+
+ Move other supported startup settings, such as `env` and `entrypoint`, into the same call. Set `enableInternet` to match the existing application. The `Container` class allows outbound Internet access unless you set `enableInternet = false`.
+
+ `ctx.container.start()` returns before the Container is ready to accept requests. Add an application-specific readiness check before sending traffic to the Container.
+
+3. **Deploy and validate the replacement application.**
+
+ Deploy both applications:
+
+
+
+ Start a replacement Container without changing production routing, for example through a test-only route that uses the `DURABLE_SANDBOX` binding. Confirm that the image, instance size, environment, entrypoint, network access, and readiness behavior match the existing application.
+
+4. **Cut traffic over to the replacement namespace.**
+
+ Update the Worker routing logic to resolve Container IDs through the replacement Durable Object binding. The same name resolves to a different Durable Object in each namespace. A Durable Object in the replacement namespace cannot access storage from the old namespace.
+
+ Choose the binding per logical Container, not per request. If requests for the same name can reach both bindings, such as during a percentage-based rollout or a [gradual deployment](/workers/versions-and-deployments/gradual-deployments/), two Containers can run for one logical sandbox and their state can diverge. For example, record which namespace each sandbox uses and read that record when routing:
+
+ ```ts
+ // isMigrated() is application code that reads a per-sandbox record.
+ const binding = (await isMigrated(sandboxName))
+ ? env.DURABLE_SANDBOX
+ : env.SANDBOX;
+ const sandbox = binding.getByName(sandboxName);
+ ```
+
+ If Durable Object state must move, complete the application-specific transfer for each sandbox before routing it to the replacement namespace.
+
+5. **Observe the replacement application.**
+
+ Keep the old application, class, and binding during the observation period. To roll back, route traffic to the old Durable Object binding again.
+
+ Writes made after cutover stay in the replacement namespace. If the application accepts writes, plan how to reconcile that data before rolling back.
+
+6. **Delete the old Container application.**
+
+ After the rollback period, remove the old Container entry from the Wrangler configuration and the old routing path from the Worker code. Deploy the updated Worker. Keep the old Durable Object class and binding until you no longer need its stored data.
+
+ Removing the entry from Wrangler configuration does not delete the existing Container application. List the applications and copy the old application ID:
+
+
+
+ Delete the old application:
+
+
+
+ This command deletes the application and its Container instances.
+
+ Delete the old Durable Object class only when its stored data is no longer needed. Deleting a Durable Object class permanently deletes its namespace and stored data. Refer to [Durable Object class exports](/durable-objects/reference/durable-objects-migrations/) or [Durable Object class migrations (legacy)](/durable-objects/reference/durable-object-class-migrations-legacy/) for the applicable cleanup process.
+
+
+
+## Related resources
+
+- [Scheduling Policies](/containers/configuration/scheduling-policy/)
+- [Image Management](/containers/guides/image-management/)
+- [Durable Object Container API](/containers/api/durable-object-container/)
+- [Migrate to the Durable Object Container API](/containers/guides/migrate-to-durable-object-container-api/)
diff --git a/src/content/docs/containers/guides/snapshots.mdx b/src/content/docs/containers/guides/snapshots.mdx
new file mode 100644
index 00000000000..913c5e0b972
--- /dev/null
+++ b/src/content/docs/containers/guides/snapshots.mdx
@@ -0,0 +1,80 @@
+---
+title: Use snapshots
+pcx_content_type: how-to
+sidebar:
+ order: 6
+description: Save and restore Container filesystems with the Durable Object scheduling policy.
+---
+
+import { TypeScriptExample } from "~/components";
+
+Snapshots let you save point-in-time filesystem state from a running [Container](/containers/). The examples on this page use the [Durable Object Container API](/containers/api/durable-object-container/).
+
+:::note[Scheduling policy requirement]
+Snapshots are only supported by Container applications that use the [`durable_object` scheduling policy](/containers/configuration/scheduling-policy/#use-the-durable-object-scheduling-policy). Applications that use the `default` scheduling policy cannot create or restore snapshots.
+:::
+
+## Create a container snapshot
+
+Use `snapshotContainer()` to capture the full container filesystem. Snapshots are immutable. If you restore a snapshot and then change files, create a new snapshot to persist those changes.
+
+:::caution[Snapshot image compatibility]
+Snapshots capture the Container's complete filesystem state, but not its memory or running processes. A snapshot is tied to the Container image version it was created from and is not portable to a different image. After updating the image, create a new snapshot from a Container running that image.
+:::
+
+The returned snapshot handle is a plain data object. You can store it and restore it later, including from another Durable Object:
+
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export class MyDurableObject extends DurableObject {
+ async saveContainer() {
+ const containerSnapshot = await this.ctx.container.snapshotContainer({
+ name: "before-upgrade",
+ });
+
+ await this.ctx.storage.put("containerSnapshot", containerSnapshot);
+ }
+}
+```
+
+
+
+## Restore a container snapshot
+
+Load the saved snapshot handle. Then, pass it to `this.ctx.container.start()` when you start another container:
+
+
+
+```ts
+import { DurableObject } from "cloudflare:workers";
+
+export class MyDurableObject extends DurableObject {
+ async restoreContainer() {
+ const containerSnapshot =
+ await this.ctx.storage.get("containerSnapshot");
+
+ if (!containerSnapshot) {
+ return;
+ }
+
+ this.ctx.container.start({ containerSnapshot, enableInternet: false });
+ }
+}
+```
+
+
+
+## Understand retention
+
+Snapshots have an implicit [30-day time-to-live](/containers/platform/limits/#snapshot-limits). Each restore refreshes that time-to-live.
+
+You cannot set a custom time-to-live yet.
+
+## Related resources
+
+- [Scheduling Policies](/containers/configuration/scheduling-policy/) - Choose how Container instances are configured and updated
+- [Durable Object Container API](/containers/api/durable-object-container/) - Full `ctx.container` API reference
+- [Lifecycle of a Container](/containers/concepts/architecture/) - Understand startup, sleep, and shutdown behavior
diff --git a/src/content/docs/containers/index.mdx b/src/content/docs/containers/index.mdx
index a8c4dd0a0c4..5bb537ffbfe 100644
--- a/src/content/docs/containers/index.mdx
+++ b/src/content/docs/containers/index.mdx
@@ -162,12 +162,12 @@ export async function startAndWaitForPort(
},
],
},
- "migrations": [
- {
- "new_sqlite_classes": ["MyContainer"],
- "tag": "v1",
+ "exports": {
+ "MyContainer": {
+ "type": "durable-object",
+ "storage": "sqlite",
},
- ],
+ },
}
```
@@ -221,6 +221,15 @@ Ship from your machine or Workers Builds, and confirm the deploy.
+
+ Choose whether image and instance configuration is managed centrally or from
+ Durable Object code.
+
+
-These are specified using the [`instance_type` property](/workers/wrangler/configuration/#containers) in your Worker's Wrangler configuration file.
+For an application that uses the [`default` scheduling policy](/containers/configuration/scheduling-policy/), specify the size with the [`instance_type` property](/workers/wrangler/configuration/#containers) in your Worker's Wrangler configuration file. For the `durable_object` policy, pass the named size to `ctx.container.start()` with the runtime `instance` property.
:::note
The `dev` and `standard` instance types are preserved for backward compatibility and are aliases for `lite` and `standard-1`, respectively.
@@ -24,7 +24,9 @@ The `dev` and `standard` instance types are preserved for backward compatibility
### 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](/workers/wrangler/configuration/#custom-instance-types) for configuration details.
+In addition to the predefined instance types, you can configure custom instance types. Field names depend on where you configure the size. Wrangler configuration for the `default` policy uses `vcpu`, `memory_mib`, and `disk_mb`. A `ctx.container.start()` call for the `durable_object` policy uses `vcpu`, `memoryMib`, and `diskMb`.
+
+Refer to the [Wrangler configuration documentation](/workers/wrangler/configuration/#custom-instance-types) or [scheduling policy documentation](/containers/configuration/scheduling-policy/#choose-an-instance-size-at-runtime) for examples.
Custom instance types have the following constraints:
@@ -54,3 +56,14 @@ The following limits apply per account:
| Total image storage per account | 50 GB [^1] |
[^1]: Delete container images with `wrangler containers delete` to free up space. If you delete a container image and then [roll back](/workers/versions-and-deployments/rollbacks/) your Worker to a previous version, this version may no longer work.
+
+## Snapshot limits
+
+The following limits apply to [Container snapshots](/containers/guides/snapshots/):
+
+| Resource | Limit |
+| --------------------- | ----------------------------------------------- |
+| Maximum snapshot size | 20 GB |
+| Snapshot retention | 30 days from creation or the most recent restore |
+
+Restoring a snapshot refreshes its 30-day time-to-live.
diff --git a/src/content/docs/workers/wrangler/configuration.mdx b/src/content/docs/workers/wrangler/configuration.mdx
index 8a3a9335131..7f07b0d9d8e 100644
--- a/src/content/docs/workers/wrangler/configuration.mdx
+++ b/src/content/docs/workers/wrangler/configuration.mdx
@@ -631,6 +631,8 @@ Each entry in `exports` is keyed by Durable Object class name. The fields on eac
- Required when `state` is `"transferred"`. The name of the target Worker that will receive the namespace.
- `transfer_from`
- Required when `state` is `"expecting-transfer"`. The name of the source Worker the namespace is being transferred from.
+- `container`
+ - Attaches a [Container](#containers) to this Durable Object. Must match the `name` of an entry in the top-level `containers` array, and requires `storage` to be `"sqlite"`. Only allowed when `state` is `"created"` or `"expecting-transfer"`. Use this instead of setting `class_name` on the Container entry.
Example:
@@ -1309,31 +1311,38 @@ You can also configure `run_worker_first` with an array of route patterns:
You can define [Containers](/containers) to run alongside your Worker using the `containers` field.
+Each Container application has a [scheduling policy](/containers/configuration/scheduling-policy/) that determines whether image and instance configuration is managed centrally or supplied by Durable Object code at runtime. The policy is immutable after the application is created.
+
:::note
-You must also define a Durable Object to communicate with your Container via Workers. This Durable Object's
-class name must match the `class_name` value in container configuration.
+Each Container application must link to a Durable Object defined in the same Worker. Set `class_name` on the Container entry, or set `name` and reference that name from [`exports..container`](#exports). Configure exactly one of these linkage directions.
:::
+### `default` scheduling policy
+
+The `default` policy stores the image and instance configuration on the Container application and manages deployments as rollouts. It is used when `scheduling_policy` is omitted or set to `"default"`.
+
The following options are available:
+- `scheduling_policy`
+ - Set to `"default"`, or omit this field.
- `image`
- The image to use for the container. This can either be a local path to a `Dockerfile`, in which case `wrangler deploy` will
build and push the image, or it can be an image reference. Supported registries are the Cloudflare Registry, Docker Hub, Amazon ECR, and Google Artifact Registry. For more information, refer to [Image Management](/containers/guides/image-management/).
-- `class_name`
+- `class_name`
- The corresponding Durable Object class name. This will make this Durable Object a container-enabled Durable Object
and allow each instance to control a container. Refer to the [Durable Object Container API](/containers/api/durable-object-container/) for details.
-- `instance_type`
- - The instance type of the container. This determines the amount of memory, CPU, and disk given to the container
- instance. The current options are `"lite"`, `"basic"`, `"standard-1"`, `"standard-2"`, `"standard-3"`, and `"standard-4"`. The default is `"lite"`. For more information,
- see the [instance types documentation](/containers/platform/limits/#instance-types).
- - To specify a custom instance type, see [here](#custom-instance-types).
-- `max_instances`
- - The maximum number of concurrent container instances you want to run at any given moment. Stopped containers do not count towards this - you may have more container instances than this number overall, but only this many actively running containers at once. If a request to start a container will exceed this limit, that request will error.
- - Defaults to 20.
- - This value is only enforced when running in production on Cloudflare's network. This limit does not apply during local development, so you may run more instances than specified.
-- `name`
+ - Omit this field when the Container is linked by `name` from `exports..container`.
+- `name`
- The name of your container. Used as an identifier. This will default to a combination of your Worker name, the class
name, and your environment.
+ - Required when `class_name` is omitted so that `exports..container` can reference the Container.
+- `instance_type`
+ - The instance type determines the amount of memory, CPU, and disk given to the container instance. Supported values are `"lite"`, `"basic"`, `"standard-1"`, `"standard-2"`, `"standard-3"`, and `"standard-4"`. The default is `"lite"`. For more information, refer to [Limits and Instance Types](/containers/platform/limits/#instance-types).
+ - To specify a custom instance type, refer to [Custom instance types](#custom-instance-types).
+- `max_instances`
+ - The maximum number of concurrent container instances. Stopped containers do not count towards this - you may have more container instances than this number overall, but only this many actively running containers at once. If a request to start a container will exceed this limit, that request will error.
+ - Defaults to 20.
+ - This value is only enforced when running in production on Cloudflare's network. This limit does not apply during local development, so you may run more instances than specified.
- `image_build_context`
- The build context of the application, by default it is the directory of `image`.
- `image_vars`
@@ -1342,9 +1351,13 @@ The following options are available:
- During a [rollout](/containers/configuration/rollouts/), minimum seconds a container instance must already have been connected to its Durable Object before it may be replaced. Defaults to `0`. Still applies with `--containers-rollout=immediate`.
- `rollout_step_percentage`
- Percentage of container instances to update at each [rollout](/containers/configuration/rollouts/) step. A single number uses that step size (`5`, `10`, `20`, `25`, `50`, or `100`). An array must contain ascending integer values from `10` through `100`, end in `100`, contain at most 10 entries, and contain no more entries than `max_instances`; its values are cumulative. Defaults to `100` if `max_instances` is omitted or less than `2`; otherwise defaults to `[10, 100]`. Override for one deploy with `--containers-rollout=immediate` (single 100% step; does not override grace period).
+- `observability`
+ - Overrides the root Worker observability configuration for this Container application.
+- `unsafe.configuration.experimental_flags`
+ - Experimental application flags.
- `ssh`
- Configuration for SSH through Wrangler. Refer to [SSH](#ssh).
-- `wrangler_ssh`
+- `wrangler_ssh`
- Deprecated alias for `ssh`. Still supported for backward compatibility.
- `authorized_keys`
- Public keys that should be added to the Container's `authorized_keys` file.
@@ -1362,6 +1375,7 @@ The following options are available:
"containers": [
{
"class_name": "MyContainer",
+ "scheduling_policy": "default", // Optional, defaults to "default". Cannot be changed after the application is created.
"image": "./Dockerfile",
"max_instances": 10,
"instance_type": "basic", // Optional, defaults to "lite"
@@ -1382,18 +1396,97 @@ The following options are available:
},
],
},
- "migrations": [
+ "exports": {
+ "MyContainer": {
+ "type": "durable-object",
+ "storage": "sqlite",
+ },
+ },
+}
+```
+
+
+
+### `durable_object` scheduling policy
+
+The `durable_object` policy is in beta. It lets each Durable Object supply its Container image or snapshot and instance size to `ctx.container.start()`. Set `scheduling_policy` to `"durable_object"` explicitly. The linked Durable Object must use SQLite storage.
+
+A `durable_object` entry accepts only the following options. Wrangler rejects every other Container application field for this policy, including `image`, `instance_type`, `max_instances`, rollout settings, and placement constraints.
+
+- `scheduling_policy`
+ - Must be set to `"durable_object"`.
+- `images`
+ - Named images that Durable Object code can access through `ctx.container.images`. A configuration can contain up to 100 named images, and each name must contain 1-128 characters.
+ - Each named image must set exactly one of `dockerfile` or `image`.
+- `images..dockerfile`
+ - Path to a local Dockerfile. `wrangler deploy` builds and pushes the image.
+- `images..build_context`
+ - Build context, relative to the Wrangler configuration file. Defaults to the directory of `dockerfile`. Only valid with `dockerfile`. Equivalent to `image_build_context` in the `default` policy.
+- `images..build_vars`
+ - Build-time variables, equivalent to `image_vars` in the `default` policy. Only valid with `dockerfile`.
+- `images..image`
+ - A digest-pinned image reference in the Cloudflare Registry.
+- `class_name`
+ - The corresponding Durable Object class name.
+ - Omit this field when the Container is linked by `name` from `exports..container`.
+- `name`
+ - The name of the Container application. Defaults to a combination of your Worker name and the class name.
+ - Required when `class_name` is omitted so that `exports..container` can reference the Container.
+- `observability`
+ - Set either `observability.enabled` or `observability.logs.enabled` to enable or disable application-wide Container logs.
+ - Instance targeting with `target_instance_count` or `target_instance_percentage` is not supported. If you omit `observability`, Wrangler preserves the application's existing setting instead of inheriting the root Worker setting.
+- `unsafe.configuration.experimental_flags`
+ - Experimental application flags. This is the only `unsafe` setting accepted with the `durable_object` policy.
+- `ssh`
+ - Configuration for SSH through Wrangler. Refer to [SSH](#ssh). The deprecated `wrangler_ssh` alias is not accepted with the `durable_object` policy.
+- `authorized_keys`
+ - Public keys that should be added to the Container's `authorized_keys` file. Refer to [Authorized keys](#authorized-keys).
+
+Name one or more images and select one from Durable Object code when the Container starts:
+
+
+
+```jsonc
+{
+ "containers": [
{
- "tag": "v1",
- "new_sqlite_classes": ["MyContainer"],
+ "class_name": "AgentComputer",
+ "scheduling_policy": "durable_object",
+ "images": {
+ "base": {
+ "dockerfile": "./container/Dockerfile",
+ "build_context": ".",
+ "build_vars": {
+ "APP_ENV": "production",
+ },
+ },
+ },
},
],
+ "durable_objects": {
+ "bindings": [
+ {
+ "name": "AGENT_COMPUTER",
+ "class_name": "AgentComputer",
+ },
+ ],
+ },
+ "exports": {
+ "AgentComputer": {
+ "type": "durable-object",
+ "storage": "sqlite",
+ },
+ },
}
```
-### Custom Instance Types
+Configure the startup image or snapshot and instance size in `ctx.container.start()`. Refer to [Scheduling policy](/containers/configuration/scheduling-policy/) for a code example.
+
+### Custom instance types
+
+In Wrangler configuration, custom instance types apply only to the `default` scheduling policy. With the `durable_object` policy, pass a custom instance object to `ctx.container.start()` instead. Refer to [Scheduling policy](/containers/configuration/scheduling-policy/).
In place of the [named instance types](/containers/platform/limits/#instance-types), you can set a custom instance type by individually configuring vCPU, memory, and disk.
See the [limits documentation](/containers/platform/limits/#custom-instance-types) for constraints on custom instance types.
@@ -1430,7 +1523,7 @@ The following options are available:
### SSH
-Configuration for SSH access to a Container instance through Wrangler. For a guide on connecting to Containers via SSH, refer to [SSH](/containers/guides/ssh/).
+Configuration for SSH access to a Container instance through Wrangler. SSH configuration applies to both the `default` and `durable_object` scheduling policies. For a guide on connecting to Containers via SSH, refer to [SSH](/containers/guides/ssh/).
The following options are available: