From 0f4df7be71cf7c94e9397bbea5689e505cb7faa5 Mon Sep 17 00:00:00 2001 From: A <258483684+la14-1@users.noreply.github.com> Date: Sat, 21 Feb 2026 23:50:46 -0800 Subject: [PATCH] feat: pre-built Docker image for OpenClaw on Fly.io (#1686) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eliminates the slow waitForCloudInit() + bun install phase by booting a pre-built image with Node.js, bun, and openclaw already installed. The image is rebuilt daily via GitHub Actions to pick up new releases. Other agents are unaffected — they still use ubuntu:24.04 + cloud-init. Co-authored-by: spawn-bot Co-authored-by: Claude Opus 4.6 --- .github/workflows/fly-docker.yml | 34 ++++++++++++++++++++++++++++++++ cli/package.json | 2 +- cli/src/fly/agents.ts | 21 +++++++++++++++----- cli/src/fly/fly.ts | 7 ++++--- cli/src/fly/main.ts | 10 ++++++++-- fly/docker/openclaw.Dockerfile | 32 ++++++++++++++++++++++++++++++ 6 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/fly-docker.yml create mode 100644 fly/docker/openclaw.Dockerfile diff --git a/.github/workflows/fly-docker.yml b/.github/workflows/fly-docker.yml new file mode 100644 index 00000000..52c3c1e7 --- /dev/null +++ b/.github/workflows/fly-docker.yml @@ -0,0 +1,34 @@ +name: Build Fly Docker Images + +on: + push: + branches: [main] + paths: + - "fly/docker/openclaw.Dockerfile" + schedule: + # Daily: pick up new openclaw releases + - cron: "0 6 * * *" + workflow_dispatch: + +permissions: + packages: write + contents: read + +jobs: + build-openclaw: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: docker/build-push-action@v6 + with: + context: . + file: fly/docker/openclaw.Dockerfile + push: true + tags: ghcr.io/openrouterteam/spawn-openclaw:latest diff --git a/cli/package.json b/cli/package.json index 887482b6..61e88101 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openrouter/spawn", - "version": "0.5.33", + "version": "0.5.34", "type": "module", "bin": { "spawn": "cli.js" diff --git a/cli/src/fly/agents.ts b/cli/src/fly/agents.ts index 829fbdc0..dd706d9a 100644 --- a/cli/src/fly/agents.ts +++ b/cli/src/fly/agents.ts @@ -21,6 +21,8 @@ import { export interface AgentConfig { name: string; + /** Custom Docker image for the Fly machine (default: ubuntu:24.04). */ + image?: string; /** If true, prompt for model selection before provisioning. */ modelPrompt?: boolean; /** Default model ID when modelPrompt is true. */ @@ -342,13 +344,22 @@ export const agents: Record = { openclaw: { name: "OpenClaw", + image: "ghcr.io/openrouterteam/spawn-openclaw:latest", modelPrompt: true, modelDefault: "openrouter/auto", - install: () => - installAgent( - "openclaw", - 'export PATH="$HOME/.bun/bin:$HOME/.local/bin:$PATH" && bun install -g openclaw && command -v openclaw', - ), + install: async () => { + logStep("Verifying openclaw installation..."); + try { + await runServer("command -v openclaw"); + logInfo("openclaw is pre-installed"); + } catch { + logInfo("openclaw not found in image, installing from scratch..."); + await installAgent( + "openclaw", + 'export PATH="$HOME/.bun/bin:$HOME/.local/bin:$PATH" && bun install -g openclaw && command -v openclaw', + ); + } + }, envVars: (apiKey) => [ `OPENROUTER_API_KEY=${apiKey}`, `ANTHROPIC_API_KEY=${apiKey}`, diff --git a/cli/src/fly/fly.ts b/cli/src/fly/fly.ts index 656c5d13..b73a83d4 100644 --- a/cli/src/fly/fly.ts +++ b/cli/src/fly/fly.ts @@ -567,11 +567,12 @@ async function createMachine( cpus: number, vmMemory: number, volumeId?: string, + image?: string, ): Promise { const kindLabel = cpuKind === "performance" ? "dedicated" : "shared"; logStep(`Creating Fly.io machine (region: ${region}, ${cpus} ${kindLabel} vCPU, ${vmMemory}MB)...`); const config: Record = { - image: "ubuntu:24.04", + image: image || "ubuntu:24.04", guest: { cpu_kind: cpuKind, cpus, memory_mb: vmMemory }, init: { exec: ["/bin/sleep", "inf"] }, auto_destroy: false, @@ -675,7 +676,7 @@ export async function listVolumes(appName: string): Promise { +export async function createServer(name: string, opts: ServerOptions, image?: string): Promise { const region = process.env.FLY_REGION || "iad"; if (!validateRegionName(region)) { @@ -698,7 +699,7 @@ export async function createServer(name: string, opts: ServerOptions): Promise/dev/null || \ + echo 'export PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH"' >> "$rc"; \ + done + +CMD ["/bin/sleep", "inf"]