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_instances to control concurrency and cost.
  • Start with the plain Worker trigger tutorial if you already have a VPS or Docker runner.