Use monitor() with the Durable Object Container API to run code after the Container exits or errors. The Container class adds named lifecycle hooks and an inactivity callback.
import { DurableObject } from "cloudflare:workers";
export class MyContainer extends DurableObject {
ready;
constructor(ctx, env) {
super(ctx, env);
ctx.blockConcurrencyWhile(() =>
ctx.container.setInactivityTimeout(5 * 60 * 1000),
);
}
async fetch(request) {
const container = this.ctx.container;
if (!container.running) {
this.ready = undefined;
}
this.ready ??= this.startAndMonitor().catch((error) => {
this.ready = undefined;
throw error;
});
await this.ready;
return container.getTcpPort(4000).fetch(request);
}
async startAndMonitor() {
const container = this.ctx.container;
if (!container.running) {
container.start();
}
this.ctx.waitUntil(
container
.monitor()
.then(() => console.log("Container stopped"))
.catch((error) => console.error("Container error:", error)),
);
const port = container.getTcpPort(4000);
let lastError;
for (let attempt = 0; attempt < 50; attempt++) {
try {
await port.fetch("http://container/");
console.log("Container successfully started");
return;
} catch (error) {
lastError = error;
await scheduler.wait(100);
}
}
throw lastError;
}
}import { DurableObject } from "cloudflare:workers";
interface Env {}
export class MyContainer extends DurableObject<Env> {
private ready: Promise<void> | undefined;
constructor(ctx: DurableObjectState, env: Env) {
super(ctx, env);
ctx.blockConcurrencyWhile(() =>
ctx.container!.setInactivityTimeout(5 * 60 * 1000),
);
}
async fetch(request: Request): Promise<Response> {
const container = this.ctx.container!;
if (!container.running) {
this.ready = undefined;
}
this.ready ??= this.startAndMonitor().catch((error: unknown) => {
this.ready = undefined;
throw error;
});
await this.ready;
return container.getTcpPort(4000).fetch(request);
}
private async startAndMonitor(): Promise<void> {
const container = this.ctx.container!;
if (!container.running) {
container.start();
}
this.ctx.waitUntil(
container
.monitor()
.then(() => console.log("Container stopped"))
.catch((error: unknown) => console.error("Container error:", error)),
);
const port = container.getTcpPort(4000);
let lastError: unknown;
for (let attempt = 0; attempt < 50; attempt++) {
try {
await port.fetch("http://container/");
console.log("Container successfully started");
return;
} catch (error) {
lastError = error;
await scheduler.wait(100);
}
}
throw lastError;
}
}import { Container } from "@cloudflare/containers";
export class MyContainer extends Container {
defaultPort = 4000;
sleepAfter = "5m";
override onStart() {
console.log("Container successfully started");
}
override onStop(stopParams) {
if (stopParams.exitCode === 0) {
console.log("Container stopped gracefully");
} else {
console.log("Container stopped with exit code:", stopParams.exitCode);
}
console.log("Container stop reason:", stopParams.reason);
}
override async onActivityExpired() {
console.log("Container became idle, stopping it now");
await this.stop();
}
override onError(error: string) {
console.log("Container error:", error);
}
}The monitor() promise in the raw API does not include an exit code or stop reason. The setInactivityTimeout() method does not invoke a callback when the timeout expires. Use the Container class lifecycle hooks when you need those higher-level events.