Container
A Container is a running Docker image running in Cloudflare's global network, managed by a Cloudflare Durable Object.
CAUTION
Cloudflare Containers is still in Beta.
You'll need:
- a
Dockerfile
for your Container - an
alchemy.run.ts
to deploy to Cloudflare - a
MyContainer
class to own a running Container Instance - a
worker.ts
that exportsfetch
and routes requests to Container Instances
Container Class
A Container's lifecycle is managed by a Durable Object class that you define.
We recommend using the Container
class from @cloudflare/containers
since it takes care of the basic container lifecycle for you:
import { Container } from "@cloudflare/containers";
import type { worker } from "../alchemy.run.ts";
export class MyContainer extends Container {
declare env: typeof worker.Env;
defaultPort = 8080; // The default port for the container to listen on
sleepAfter = "3m"; // Sleep the container if no requests are made in this timeframe
envVars = {
MESSAGE: "I was passed in via the container class!",
};
override onStart() {
console.log("Container successfully started");
}
override onStop() {
console.log("Container successfully shut down");
}
override onError(error: unknown) {
console.log("Container error:", error);
}
}
Container Resource
Now, create a Container
Resource in your alchemy.run.ts
file and connect it to your MyContainer
class:
import { Container, Worker } from "alchemy/cloudflare";
import { Image } from "alchemy/docker";
// import the type of your Container's implementation
import type { MyContainer } from "./src/container.ts";
const container = await Container<MyContainer>("my-container", {
className: "MyContainer", // <- and ^
});
This will build your Dockerfile and prepare it for publishing to Cloudflare's Image Registry.
TIP
The default behavior is effectively docker build . -t my-container
but you can customize the configuration:
const container = await Container<MyContainer>("my-container", {
className: "MyContainer",
name: "your-container",
tag: "some-tag",
build: {
context: import.meta.dir,
dockerfile: "Dockerfile.dev",
},
});
Bind to Worker
To deploy the Container
to Cloudflare, you need to bind it to a Worker
:
export const worker = await Worker("my-worker", {
name: "my-worker",
entrypoint: "./src/worker.ts",
bindings: {
MY_CONTAINER: container,
},
});
NOTE
Binding a Container to a Worker will also bind a Durable Object Namespace to the Worker.
Route Requests
To route requests, have your Worker's fetch
handler resolve a Durable Object instance and proxy the request
to it:
import { getContainer } from "@cloudflare/containers";
import type { worker } from "../alchemy.run.ts";
// the class must be exported for Cloudflare
export { MyContainer } from "./container.ts";
export default {
async fetch(request: Request, env: typeof worker.Env): Promise<Response> {
const container = getContainer(env.CONTAINER, "container");
return container.fetch(request);
},
};
TIP
Notice how the type of our Worker environment is inferred with typeof worker.Env
, see the Type-safe Bindings documentation for more information.
Complex Routing
Cloudflare's unique design allows you to implement your own routing strategies in pure JavaScript.
Round-Robin
For example, you can round-robin requests across a fixed pool by simply generating a random instance ID between 0 and the number of instances:
export async function loadBalance<T extends Container>(
binding: DurableObjectNamespace<T>,
instances = 3
): Promise<DurableObjectStub<T>> {
const containerId = binding.idFromName(`instance-${rand(0, instances)}`);
const container = binding.get(containerId);
return container.fetch(request);
}