Skip to content

Cron Container

Running a container on a schedule using Cron Triggers

Last updated View as MarkdownAgent setup

To launch a container on a schedule, you can use a Workers Cron Trigger.

For a full example, see the Cron Container Template.

Use a cron expression in your Wrangler config to specify the schedule:

{
	"name": "cron-container",
	"main": "src/index.ts",
	"triggers": {
		"crons": [
			"*/2 * * * *" // Run every 2 minutes
		]
	},
	"containers": [
		{
			"class_name": "CronContainer",
			"image": "./Dockerfile"
		}
	],
	"durable_objects": {
		"bindings": [
			{
				"class_name": "CronContainer",
				"name": "CRON_CONTAINER"
			}
		]
	},
	"migrations": [
		{
			"new_sqlite_classes": ["CronContainer"],
			"tag": "v1"
		}
	]
}
name = "cron-container"
main = "src/index.ts"

[triggers]
crons = [ "*/2 * * * *" ]

[[containers]]
class_name = "CronContainer"
image = "./Dockerfile"

[[durable_objects.bindings]]
class_name = "CronContainer"
name = "CRON_CONTAINER"

[[migrations]]
new_sqlite_classes = [ "CronContainer" ]
tag = "v1"

Then call the Container from the scheduled() handler in the Worker. The raw API example expects the Container to expose a POST /run endpoint that starts the scheduled task.

src/index.jsjs
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;
		await container.setInactivityTimeout(10_000);

		if (!container.running) {
			container.start();
		}

		const port = container.getTcpPort(8080);
		let lastError;
		for (let attempt = 0; attempt < 50; attempt++) {
			try {
				await port.fetch("http://container/");
				lastError = undefined;
				break;
			} catch (error) {
				lastError = error;
				await scheduler.wait(100);
			}
		}
		if (lastError) {
			throw lastError;
		}

		const response = await port.fetch("http://container/run", {
			method: "POST",
			body: JSON.stringify({ startTime }),
			headers: { "content-type": "application/json" },
		});
		if (!response.ok) {
			throw new Error(`Container returned ${response.status}`);
		}
	}
}

export default {
	async fetch() {
		return new Response("This Worker runs a scheduled Container task.");
	},

	async scheduled(_controller, env) {
		await env.CRON_CONTAINER.getByName("cron").run(new Date().toISOString());
	},
};
src/index.tsts
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!;
		await container.setInactivityTimeout(10_000);

		if (!container.running) {
			container.start();
		}

		const port = container.getTcpPort(8080);
		let lastError: unknown;
		for (let attempt = 0; attempt < 50; attempt++) {
			try {
				await port.fetch("http://container/");
				lastError = undefined;
				break;
			} catch (error) {
				lastError = error;
				await scheduler.wait(100);
			}
		}
		if (lastError) {
			throw lastError;
		}

		const response = await port.fetch("http://container/run", {
			method: "POST",
			body: JSON.stringify({ startTime }),
			headers: { "content-type": "application/json" },
		});
		if (!response.ok) {
			throw new Error(`Container returned ${response.status}`);
		}
	}
}

export default {
	async fetch(): Promise<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().toISOString());
	},
};
import { Container, getContainer } from "@cloudflare/containers";

export class CronContainer extends Container {
  sleepAfter = '10s';

  override onStart() {
    console.log('Starting container');
  }

  override onStop() {
    console.log('Container stopped');
  }
}

export default {
	async fetch(): Promise<Response> {
		return new Response("This Worker runs a cron job to execute a container on a schedule.");
	},

	async scheduled(_controller: ScheduledController, env: { CRON_CONTAINER: DurableObjectNamespace<CronContainer> }) {
		const container = getContainer(env.CRON_CONTAINER);
		await container.start({
			envVars: {
				MESSAGE: "Start Time: " + new Date().toISOString(),
			},
		});
	},
};

Was this helpful?