Container
Container
Section titled “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
Section titled “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
Section titled “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 implementationimport 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",},});
Adopting Existing Containers
Section titled “Adopting Existing Containers”By default, if a container application with the same name already exists, Alchemy will throw an error. However, you can use the adopt
property to take over management of an existing container application:
const container = await Container<MyContainer>("my-container", { className: "MyContainer", adopt: true, // Will adopt existing container instead of failing});
Development Mode
Section titled “Development Mode”Container resources have special behavior in development mode to support local testing:
Local Development
Section titled “Local Development”By default, when running in dev mode (using --dev
flag), Container images are not pushed to Cloudflare’s registry. Instead, they use a local cloudflare-dev/
prefix that Miniflare can read from your local Docker daemon:
const container = await Container<MyContainer>("my-container", { className: "MyContainer", // In dev mode: uses local Docker image with cloudflare-dev/ prefix // In production: pushes to Cloudflare's registry});
Remote Development
Section titled “Remote Development”If you need to use a Container with a remote Worker during development, set dev: { remote: true }
to push the image to Cloudflare:
const container = await Container<MyContainer>("my-container", { className: "MyContainer", dev: { remote: true // Forces push to Cloudflare registry even in dev mode }});
[!WARNING] Limitations with Remote Containers
- If you set
dev: { remote: true }
on a Container, it cannot be used as a local binding in development- Remote container bindings are not supported for locally emulated workers
Startup Warnings
Section titled “Startup Warnings”When calling container.fetch()
while the container is still starting up, you may see this warning in the console:
Error checking if container is ready: connect(): Connection refused: container port not found. Make sure you exposed the port in your container definition.
This warning can be safely ignored - the binding still works correctly and this is expected behavior during container startup.
Bind to Worker
Section titled “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
Section titled “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 Cloudflareexport { 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
Section titled “Complex Routing”Cloudflare’s unique design allows you to implement your own routing strategies in pure JavaScript.
Round-Robin
Section titled “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);}