Run a short container task every two minutes with a Workers Cron Trigger. The task prints its scheduled time, then exits.
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "cron-container",
"main": "src/index.ts",
// Set this to today's date
"compatibility_date": "2026-10-05",
"observability": {
"enabled": true
},
"triggers": {
"crons": [
"*/2 * * * *"
]
},
"containers": [
{
"class_name": "CronContainer",
"scheduling_policy": "durable_object",
"images": {
"base": {
"dockerfile": "./Dockerfile"
}
}
}
],
"durable_objects": {
"bindings": [
{
"name": "CRON_CONTAINER",
"class_name": "CronContainer"
}
]
},
"exports": {
"CronContainer": {
"type": "durable-object",
"storage": "sqlite"
}
}
}name = "cron-container"
main = "src/index.ts"
# Set this to today's date
compatibility_date = "2026-10-05"
[observability]
enabled = true
[triggers]
crons = ["*/2 * * * *"]
[[containers]]
class_name = "CronContainer"
scheduling_policy = "durable_object"
[containers.images.base]
dockerfile = "./Dockerfile"
[[durable_objects.bindings]]
name = "CRON_CONTAINER"
class_name = "CronContainer"
[exports.CronContainer]
type = "durable-object"
storage = "sqlite"import { DurableObject } from "cloudflare:workers";
export class CronContainer extends DurableObject {
currentRun;
run(startTime) {
this.currentRun ??= this.runOnce(startTime).finally(() => {
this.currentRun = undefined;
});
return this.currentRun;
}
async runOnce(startTime) {
const container = this.ctx.container;
if (!container.running) {
container.start({
image: container.images.base,
instance: "lite",
enableInternet: false,
env: { MESSAGE: `Scheduled time: ${startTime}` },
});
}
await container.monitor();
}
}
export default {
fetch() {
return new Response("This Worker runs a scheduled Container task.");
},
async scheduled(controller, env) {
await env.CRON_CONTAINER.getByName("cron").run(
new Date(controller.scheduledTime).toISOString(),
);
},
};import { DurableObject } from "cloudflare:workers";
interface Env {
CRON_CONTAINER: DurableObjectNamespace<CronContainer>;
}
export class CronContainer extends DurableObject<Env> {
private currentRun: Promise<void> | undefined;
run(startTime: string): Promise<void> {
this.currentRun ??= this.runOnce(startTime).finally(() => {
this.currentRun = undefined;
});
return this.currentRun;
}
private async runOnce(startTime: string): Promise<void> {
const container = this.ctx.container!;
if (!container.running) {
container.start({
image: container.images.base,
instance: "lite",
enableInternet: false,
env: { MESSAGE: `Scheduled time: ${startTime}` },
});
}
await container.monitor();
}
}
export default {
fetch(): Response {
return new Response("This Worker runs a scheduled Container task.");
},
async scheduled(controller: ScheduledController, env: Env): Promise<void> {
await env.CRON_CONTAINER.getByName("cron").run(
new Date(controller.scheduledTime).toISOString(),
);
},
} satisfies ExportedHandler<Env>;All triggers use the same Durable Object. If a run is still active, another trigger waits for that run instead of starting a second task. This example does not queue overlapping runs or guarantee exactly-once execution. Make tasks idempotent when they modify external data.
monitor() reports completion or failure, not HTTP readiness. Keep this task short enough to finish within the Cron Trigger execution limits.
FROM alpine:3.20
CMD ["sh", "-c", "printf '%s\\n' \"$MESSAGE\""]Start a Docker-compatible engine and use Wrangler 4.136.0 or later.
npm i -D wrangleryarn add -D wranglerpnpm add -D wranglerbun add -d wranglernpx wrangler dev --test-scheduledyarn wrangler dev --test-scheduledpnpm wrangler dev --test-scheduledIn another terminal, trigger the scheduled handler:
curl 'http://localhost:8787/__scheduled?cron=*/2+*+*+*+*'Check the container logs for the scheduled time. After deployment, Cron Triggers invoke the handler automatically.