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.
- Replace the
Containerclass. TheContainerclass does not support thedurable_objectpolicy. If your existing class extendsContainer, the replacement class must use the Durable Object Container API directly. Rebuild anyContainerclass helpers your application relies on, such as port readiness checks, request proxying, and sleep timeouts. Refer to Migrate to the Durable Object Container API. - Check how the Worker declares Durable Object classes. A Worker uses either the
exportsfield or the legacymigrationsarray, not both. Declare the replacement class with the same mechanism the Worker already uses. Moving frommigrationstoexportscannot be undone. If you want to move, do it as a separate change. Refer to Migrate from the legacymigrationsflow. - Move external images to the Cloudflare managed registry. Named images with the
durable_objectpolicy must use a Dockerfile or a digest-pinned reference in the Cloudflare managed registry. If the existingimagereferences Docker Hub, Amazon ECR, or Google Artifact Registry, push the image to the Cloudflare managed registry first.
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 |
max_instances |
Not supported. Running instances count toward 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.
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: Uselite.standard: Usestandard-1.basic: Chooseliteorstandard-1. Custom instance types require at least 1 vCPU, so you cannot reproducebasicwith 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.
-
Add a new Durable Object class and Container application.
You cannot move the existing application by changing its
scheduling_policytodurable_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.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 differentname, andclass_nameset 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
nameorclass_namecan cause Wrangler to create a new application instead of updating the existing one.The following example uses
exports. If the Worker uses the legacymigrationsarray, add a new migration withnew_sqlite_classes: ["DurableSandbox"]instead.{ "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", }, }, }name = "sandbox-worker" main = "src/index.ts" compatibility_date = "2026-09-29" [[containers]] class_name = "Sandbox" image = "./container/Dockerfile" instance_type = "standard-2" max_instances = 10 [[containers]] name = "sandbox-durable-object" class_name = "DurableSandbox" scheduling_policy = "durable_object" [containers.images.base] dockerfile = "./container/Dockerfile" [[durable_objects.bindings]] name = "SANDBOX" class_name = "Sandbox" [[durable_objects.bindings]] name = "DURABLE_SANDBOX" class_name = "DurableSandbox" [exports.Sandbox] type = "durable-object" storage = "sqlite" [exports.DurableSandbox] type = "durable-object" storage = "sqlite" -
Move startup configuration into the replacement class.
Select the image and instance size when the replacement Durable Object starts its Container:
src/index.jsjs 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, }); } }src/index.tsts 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
envandentrypoint, into the same call. SetenableInternetto match the existing application. TheContainerclass allows outbound Internet access unless you setenableInternet = 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. -
Deploy and validate the replacement application.
Deploy both applications:
npx wrangler deployyarn wrangler deploypnpm wrangler deployStart a replacement Container without changing production routing, for example through a test-only route that uses the
DURABLE_SANDBOXbinding. Confirm that the image, instance size, environment, entrypoint, network access, and readiness behavior match the existing application. -
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, 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:
// 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.
-
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.
-
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:
npx wrangler containers listyarn wrangler containers listpnpm wrangler containers listDelete the old application:
npx wrangler containers delete <OLD_APPLICATION_ID>yarn wrangler containers delete <OLD_APPLICATION_ID>pnpm wrangler containers delete <OLD_APPLICATION_ID>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 or Durable Object class migrations (legacy) for the applicable cleanup process.