Provider docs used

Cloudflare Containers are configured from Wrangler with a container image, Durable Object binding, and migration. The container image must be able to run on linux/amd64, and Worker code routes requests to container instances. Keep the current provider guide open: Cloudflare Containers getting started.

When to use this

Use Cloudflare Containers when a Worker trigger is not enough and you want Cloudflare to run the containerized runner. Start with a VPS or Docker host if you need persistent workspaces, large dependency caches, private VPC access, or simple shell debugging.

Step 1: Create the runner container

The container should expose a small HTTP API. The API receives an allowlisted task and starts Autohand Code inside the container.

FROM node:22-bookworm

RUN apt-get update \
  && apt-get install -y --no-install-recommends git openssh-client ca-certificates build-essential \
  && rm -rf /var/lib/apt/lists/*

RUN npm install -g autohand-cli

WORKDIR /runner
COPY server.mjs /runner/server.mjs

EXPOSE 8787
CMD ["node", "/runner/server.mjs"]

Step 2: Add a minimal runner API

// server.mjs
import { createServer } from "node:http";
import { spawn } from "node:child_process";

const prompts = {
  review: "Clone or update the configured repository, review recent changes, and summarize action items"
};

createServer((req, res) => {
  if (req.method !== "POST") {
    res.writeHead(405);
    res.end("method not allowed");
    return;
  }

  const job = spawn("autohand", [
    "-p",
    prompts.review,
    "--restricted",
    "--output-format",
    "stream-json"
  ], { env: process.env });

  job.stdout.on("data", chunk => process.stdout.write(chunk));
  job.stderr.on("data", chunk => process.stderr.write(chunk));
  res.writeHead(202);
  res.end("queued\n");
}).listen(8787, "0.0.0.0");

Step 3: Configure Wrangler

Cloudflare Containers use Worker configuration plus a Durable Object class. The image can point at a local Dockerfile or a qualified image reference.

name = "autohand-container-runner"
main = "src/index.js"
compatibility_date = "2026-06-18"

[[containers]]
class_name = "AutohandCodeContainer"
image = "./Dockerfile"
max_instances = 5

[[durable_objects.bindings]]
name = "AUTOHAND_CONTAINER"
class_name = "AutohandCodeContainer"

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

Step 4: Route requests to containers

import { Container, getContainer } from "@cloudflare/containers";

export class AutohandCodeContainer extends Container {
  defaultPort = 8787;
  sleepAfter = "10m";
}

export default {
  async fetch(request, env) {
    if (request.method !== "POST") {
      return new Response("method not allowed", { status: 405 });
    }

    const repo = request.headers.get("x-repo") || "default";
    const container = getContainer(env.AUTOHAND_CONTAINER, repo);
    return container.fetch(request);
  }
};

Step 5: Add secrets and deploy

Use Worker secrets for tokens. Pass only the values the container needs.

wrangler secret put OPENROUTER_API_KEY
wrangler secret put GIT_DEPLOY_KEY
wrangler deploy

Operations checklist

  • Design jobs to be resumable because containers may sleep after idle time.
  • Keep repository clones and dependency caches small unless you intentionally add durable storage elsewhere.
  • Use allowlisted tasks instead of arbitrary prompts from public requests.
  • Use max_instances to control concurrency and cost.
  • Start with the plain Worker trigger tutorial if you already have a VPS or Docker runner.