Durable Object Container API
Last updated Sep 30, 2026 Each container 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.
The API documented on this page is available on this.ctx.container inside any Durable Object class that has a container binding. Use it for direct control over the container process.
Note
We recommend starting new applications with the Durable Object Container API. It lets you run container workloads alongside the Durable Object's persistent storage, alarms, and request handling. Use ctx.container to control when the container starts, receives traffic, and stops, while the Durable Object manages application state and coordination. For existing applications that use the Container class, refer to Migrate to the Durable Object Container API.
Your Durable Object also has access to SQLite storage through this.ctx.storage, alarms, and all other Durable Object APIs. Use storage to preserve configuration and state across container restarts. Use alarms to schedule work without keeping the container running.
index.jsjs
import { DurableObject } from "cloudflare:workers";
export class MyDurableObject extends DurableObject {
async start() {
const container = this.ctx.container;
if (!container) {
throw new Error("No container is configured for this Durable Object");
}
if (!container.running) {
// 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());
}
}
async scheduleStart(timestamp) {
await this.ctx.storage.setAlarm(timestamp);
}
async alarm() {
await this.start();
}
}
index.tsts
import { DurableObject } from "cloudflare:workers";
interface Env {}
export class MyDurableObject extends DurableObject<Env> {
async start(): Promise<void> {
const container = this.ctx.container;
if (!container) {
throw new Error("No container is configured for this Durable Object");
}
if (!container.running) {
// 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());
}
}
async scheduleStart(timestamp: number): Promise<void> {
await this.ctx.storage.setAlarm(timestamp);
}
async alarm(): Promise<void> {
await this.start();
}
}
Attributes
images durable_object policy only
images is a read-only map generated from the images field in the application's Wrangler configuration. 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.
const image = this.ctx.container.images.base;
Pass a value from this map as the image option to start().
running
running is true when the container is running. It does not confirm that the container is ready to accept requests.
this.ctx.container.running;
Methods
start
start() boots a container. It returns before the container is ready to accept requests. Confirm readiness before sending traffic.
start() returns once it validates its options. To catch later errors, including a container that fails to start, use monitor().
The required options depend on the application's scheduling policy.
// `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, conditionally required): Startup options. Required for applications that use thedurable_objectscheduling policy and optional for applications that use thedefaultscheduling policy.env(Record<string, string>, optional): Environment variables to pass to the container. Processes started withexec()do not receive these variables, exceptPATH.entrypoint(string[], optional): Command and arguments to run in the container.enableInternet(boolean, required): Whether to allow outbound Internet access. Required when you passoptions.image(string, conditionally required) durable_object policy only: Image reference to start. Required unless you passcontainerSnapshot. Pass thecloudflare/debian-trixiemanaged image or a value fromctx.container.images.instance(string | object, optional) durable_object policy only: Instance size. Pass"lite","standard-1","standard-2","standard-3","standard-4", or a custom object withvcpu,memoryMib, anddiskMbproperties.memoryMibis in mebibytes anddiskMbis in megabytes. Both must be positive integers. Defaults to"lite".containerSnapshot(ContainerSnapshotRestoreParams, optional) durable_object policy only: Snapshot handle to restore before startup. Pass theContainerSnapshotreturned bysnapshotContainer(), or an object containing itsid. You cannot pass bothcontainerSnapshotandimage.labels(Record<string, string>, optional): Up to 10 labels thatinspect()returns. Label names must contain 1 to 16 bytes. Label values can contain up to 64 bytes. Names and values cannot contain control characters.
monitor() rejects when a container that uses the durable_object scheduling policy starts without image or containerSnapshot.
Return values
void: No return value.
Exceptions
start()throws anErrorwhen the container is already running.start()throws aTypeErrorwhen bothimageandcontainerSnapshotare set, when either is empty, or wheninstancenames an unknown type.start()throws aRangeErrorwhen a custominstancevalue is missing or invalid.start()throws anErrorwhenlabelsexceed the label limits, when an environment variable name contains=, or when an environment variable name or value contains a null character.
inspect
inspect() returns the image and labels for a running container. It returns null when no container is running.
const containerInfo = await this.ctx.container.inspect();
Parameters
- None.
Return values
Promise<ContainerInfo | null>: Resolves withnullwhen no container is running, including after a failed start or afterdestroy(). Otherwise, it resolves with aContainerInfoobject containing:image(string): Image reference passed tostart(). The value is an empty string while the container is starting and for a container restored fromcontainerSnapshot.labels(Record<string, string>): Labels passed tostart().
exec
exec() starts another process inside an already-running container. It does not start a stopped container. It waits for a container that is still starting.
exec(
cmd: string[],
options?: ContainerExecOptions,
): Promise<ExecProcess>
exec() 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 checks that the container is running before executing a command:
import { DurableObject } from "cloudflare:workers";
export class MyDurableObject extends DurableObject {
async runCommand() {
const container = this.ctx.container;
if (!container) {
throw new Error("No container is configured for this Durable Object");
}
if (!container.running) {
throw new Error("Container is not running");
}
const process = await container.exec(["node", "--version"]);
const output = await process.output();
return {
pid: process.pid,
exitCode: output.exitCode,
stdout: new TextDecoder().decode(output.stdout),
};
}
}
import { DurableObject } from "cloudflare:workers";
interface Env {}
export class MyDurableObject extends DurableObject<Env> {
async runCommand() {
const container = this.ctx.container;
if (!container) {
throw new Error("No container is configured for this Durable Object");
}
if (!container.running) {
throw new Error("Container is not running");
}
const process = await 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", optional): Source for standard input. Use"pipe"to write through the returnedstdinstream. When omitted, standard input closes and sends end-of-file (EOF).stdout("pipe" | "ignore", optional, default"pipe"): Captures or discards standard output.stderr("pipe" | "ignore" | "combined", optional, default"pipe"): Captures, discards, or merges standard error into standard output. The"combined"value requiresstdout: "pipe". Combined output does not guarantee ordering between its source streams.cwd(string, optional): Working directory for the process.env(Record<string, string>, optional): Environment variables for the process. Other variables fromstart()or the image are not inherited, except forPATH. Unless overridden here,PATHuses the container's startup value, including any override passed tostart().user(string, optional): Numeric Linux user and group IDs for the process, asuid:gid. When omitted, the process runs as the image user.signal(AbortSignal, optional): Aborting the signal sendsSIGKILLto the process. An already-aborted signal makesexec()throw anAbortError.pty(boolean | { cols?: number; rows?: number }, optional): Runs the process attached to a pseudo-terminal.trueuses 80 columns and 24 rows.
The following call runs a command as user 1000 and group 1000. The file it creates belongs to 1000:1000:
const process = await this.ctx.container.exec(["touch", "/tmp/notes.txt"], {
user: "1000:1000",
});
Caution
Pass both IDs in user. A user ID without a group ID runs the process as root, and a user or group name makes exec() reject with an internal error.
user does not restrict a process in a deployed container with the durable_object scheduling policy. Every process has the same Linux capabilities as root, regardless of user. File permissions do not stop the process, and it can switch back to root. The IDs set the owner of files that the process creates. In containers with the default scheduling policy, other users have no capabilities, and file permissions apply.
With pty, stdin defaults to "pipe", and stderr defaults to "combined" and cannot take another value. Standard output and standard error arrive together on stdout. The terminal converts line endings to \r\n.
