Headless Mode
Run Autohand Code on Cloudflare Containers
Use Cloudflare Containers when you need Worker-routed HTTP control plus a container image that can run a Linux process.
Provider docs used
This tutorial follows provider documentation for current container image, Wrangler, and routing configuration. Keep this external guide open while you work.
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_instancesto control concurrency and cost. - Start with the plain Worker trigger tutorial if you already have a VPS or Docker runner.