Containers
Run a full Base worker in a Docker container on Cloudflare, for native dependencies and heavy work.
Some work doesn't fit a V8 isolate: native libraries (image processing, ffmpeg), long computations, big memory. Cloudflare Containers run Docker images next to your workers, and in Base, the thing inside the container is another ordinary Base worker, so your programming model doesn't change at the boundary.
The layout
A container worker is a worker with a container/ subfolder holding its own entry, settings, and Dockerfile:
The outer worker
Containers sit on Durable Objects, so the outer entry declares a container class:
wrangler.toml pairs a containers entry with a durable_objects binding for the same class, plus new_sqlite_classes in the migrations block:
The inner worker
container/index.ts and container/settings.ts are a completely normal Base worker (BaseWorker.create(settings), services, RPC, the lot) with server.port matching defaultPort. Native dependencies go in container/package.json and are installed inside the image, where their bindings match the container's architecture.
The Dockerfile consumes the CLI's build output: base container dist bundles the worker script and a Node bootstrap into container/dist/, and the image's command is node ./bootstrap.js <worker-name> ./workers ./<worker>.js. You rarely run that yourself — develop, deploy, and bundle build the container automatically whenever a container/ folder exists.
Calling into the container
Same shape as Durable Objects — a provider yields handles with a typed RPC client:
With no input, containers default to a shared singleton instance (unlike DOs' unique-per-call); getContainer({ name }) pins by name, and getRandomContainer(instances) fans out across a small pool. Because the interface is a shared TypeScript type, calling into the container reads exactly like calling any other worker — the Docker boundary disappears from the code.