Skip to content

Commit 849e980

Browse files
AhmedTMMclaude
andauthored
refactor: remove Docker install wrapper, tarballs replace it (#2244)
Docker delivery is superseded by the tarball approach (#2232) which is faster (curl|tar ~5-15s vs Docker install ~30s + pull ~60s) and works on every cloud without Docker as a dependency. - Remove tryInstallFromDocker, withDockerInstall, DOCKER_IMAGE_PREFIX - Remove dockerImage and slowInstall from AgentConfig - Remove Docker cloud-init from DigitalOcean - Unwrap openclaw and zeroclaw to direct install (tarball is tried first in orchestrate.ts, these are the fallback) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8b99fe0 commit 849e980

5 files changed

Lines changed: 9 additions & 107 deletions

File tree

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@openrouter/spawn",
3-
"version": "0.15.0",
3+
"version": "0.15.1",
44
"type": "module",
55
"bin": {
66
"spawn": "cli.js"

packages/cli/src/digitalocean/digitalocean.ts

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -740,29 +740,16 @@ export async function promptDoRegion(): Promise<string> {
740740

741741
// ─── Provisioning ────────────────────────────────────────────────────────────
742742

743-
function getCloudInitUserdata(tier: CloudInitTier = "full", agentName?: string): string {
743+
function getCloudInitUserdata(tier: CloudInitTier = "full"): string {
744744
const packages = getPackagesForTier(tier);
745745
const lines = [
746746
"#!/bin/bash",
747747
"set -e",
748748
"export HOME=/root",
749749
"export DEBIAN_FRONTEND=noninteractive",
750+
"apt-get update -y",
751+
`apt-get install -y --no-install-recommends ${packages.join(" ")}`,
750752
];
751-
752-
if (agentName) {
753-
// Install Docker FIRST (uses apt internally), then start image pull in background.
754-
// The pull runs in parallel with the remaining apt-get/node/bun installs below.
755-
if (!/^[a-z0-9-]+$/.test(agentName)) {
756-
throw new Error(`Invalid agent name: ${agentName}`);
757-
}
758-
lines.push(
759-
"curl -fsSL https://get.docker.com | sh",
760-
`nohup docker pull "ghcr.io/openrouterteam/spawn-${agentName}:latest" > /tmp/docker-pull.log 2>&1 &`,
761-
);
762-
}
763-
764-
// Install remaining packages (runs in parallel with docker pull above)
765-
lines.push("apt-get update -y", `apt-get install -y --no-install-recommends ${packages.join(" ")}`);
766753
if (needsNode(tier)) {
767754
lines.push(`${NODE_INSTALL_CMD} || true`);
768755
}
@@ -784,7 +771,6 @@ export async function createServer(
784771
tier?: CloudInitTier,
785772
dropletSize?: string,
786773
region?: string,
787-
agentName?: string,
788774
): Promise<void> {
789775
const size = dropletSize || process.env.DO_DROPLET_SIZE || "s-2vcpu-4gb";
790776
const effectiveRegion = region || process.env.DO_REGION || "nyc3";
@@ -811,7 +797,7 @@ export async function createServer(
811797
size,
812798
image,
813799
ssh_keys: sshKeyIds,
814-
user_data: getCloudInitUserdata(tier, agentName),
800+
user_data: getCloudInitUserdata(tier),
815801
backups: false,
816802
monitoring: false,
817803
});

packages/cli/src/digitalocean/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ async function main() {
5757
},
5858
async createServer(name: string, spawnId?: string) {
5959
process.env.SPAWN_ID = spawnId || "";
60-
await createDroplet(name, agent.cloudInitTier, dropletSize, region, agent.slowInstall ? agentName : undefined);
60+
await createDroplet(name, agent.cloudInitTier, dropletSize, region);
6161
},
6262
getServerName,
6363
async waitForReady() {

packages/cli/src/shared/agent-setup.ts

Lines changed: 3 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -524,75 +524,6 @@ const NPM_PREFIX_SETUP =
524524
'mkdir -p ~/.npm-global/bin; _NPM_G_FLAGS="--prefix $HOME/.npm-global"; fi; ' +
525525
'export PATH="$HOME/.npm-global/bin:$PATH"';
526526

527-
// ─── Docker Image Extraction ──────────────────────────────────────────────────
528-
529-
const DOCKER_IMAGE_PREFIX = "ghcr.io/openrouterteam/spawn-";
530-
531-
/**
532-
* Try to extract a pre-built agent from a Docker image pulled during cloud-init.
533-
* Returns true if extraction succeeded, false if Docker/image unavailable.
534-
*
535-
* How it works:
536-
* 1. Check if Docker is installed and the image was pulled
537-
* 2. Create a temporary container from the image
538-
* 3. Copy /root/. from the container to /root/ on the host
539-
* 4. Remove the temporary container
540-
*
541-
* The agent then runs natively on the host (not in a container).
542-
*/
543-
async function tryInstallFromDocker(runner: CloudRunner, agentName: string, dockerImage: string): Promise<boolean> {
544-
logStep(`Checking for pre-built Docker image (${agentName})...`);
545-
const script = [
546-
// Bail if Docker isn't installed
547-
"command -v docker >/dev/null 2>&1 || { echo '==> Docker not installed'; exit 1; }",
548-
// Poll for image availability — cloud-init started the pull in the background.
549-
// We can't rely on pgrep because the docker CLI exits while dockerd continues pulling.
550-
"_elapsed=0; _max=300",
551-
`while ! docker images -q "${dockerImage}" 2>/dev/null | grep -q .; do`,
552-
` if [ $_elapsed -ge $_max ]; then echo "==> Timed out waiting for ${dockerImage}"; exit 1; fi`,
553-
' if [ $_elapsed -eq 0 ]; then echo "==> Waiting for Docker image pull to complete..."; fi',
554-
" sleep 5; _elapsed=$((_elapsed + 5))",
555-
"done",
556-
// Create temp container, copy only known agent directories, clean up
557-
`_cid=$(docker create "${dockerImage}")`,
558-
'docker cp "$_cid":/root/.claude /root/ 2>/dev/null || true',
559-
'docker cp "$_cid":/root/.bun /root/ 2>/dev/null || true',
560-
'docker cp "$_cid":/root/.local /root/ 2>/dev/null || true',
561-
'docker cp "$_cid":/root/.npm /root/ 2>/dev/null || true',
562-
'docker cp "$_cid":/root/.cargo /root/ 2>/dev/null || true',
563-
'docker cp "$_cid":/root/.opencode /root/ 2>/dev/null || true',
564-
'docker rm "$_cid" >/dev/null',
565-
'echo "==> Agent extracted from Docker image"',
566-
].join("\n");
567-
568-
try {
569-
await runner.runServer(script, 600);
570-
logInfo(`${agentName} extracted from Docker image`);
571-
return true;
572-
} catch {
573-
logInfo("Docker image not available, falling back to normal install");
574-
return false;
575-
}
576-
}
577-
578-
/**
579-
* Wrap an agent's install function with Docker image extraction.
580-
* Tries Docker first; falls back to the original install if unavailable.
581-
*/
582-
function withDockerInstall(
583-
runner: CloudRunner,
584-
agentName: string,
585-
dockerImage: string,
586-
originalInstall: () => Promise<void>,
587-
): () => Promise<void> {
588-
return async () => {
589-
const extracted = await tryInstallFromDocker(runner, agentName, dockerImage);
590-
if (!extracted) {
591-
await originalInstall();
592-
}
593-
};
594-
}
595-
596527
// ─── Default Agent Definitions ───────────────────────────────────────────────
597528

598529
const ZEROCLAW_INSTALL_URL =
@@ -603,7 +534,6 @@ function createAgents(runner: CloudRunner): Record<string, AgentConfig> {
603534
claude: {
604535
name: "Claude Code",
605536
cloudInitTier: "minimal",
606-
dockerImage: `${DOCKER_IMAGE_PREFIX}claude:latest`,
607537
preProvision: promptGithubAuth,
608538
install: () => installClaudeCode(runner),
609539
envVars: (apiKey) => [
@@ -622,7 +552,6 @@ function createAgents(runner: CloudRunner): Record<string, AgentConfig> {
622552
codex: {
623553
name: "Codex CLI",
624554
cloudInitTier: "node",
625-
dockerImage: `${DOCKER_IMAGE_PREFIX}codex:latest`,
626555
preProvision: promptGithubAuth,
627556
install: () =>
628557
installAgent(
@@ -642,20 +571,17 @@ function createAgents(runner: CloudRunner): Record<string, AgentConfig> {
642571
openclaw: {
643572
name: "OpenClaw",
644573
cloudInitTier: "full",
645-
dockerImage: `${DOCKER_IMAGE_PREFIX}openclaw:latest`,
646-
slowInstall: true,
647574
preProvision: promptGithubAuth,
648575
modelPrompt: true,
649576
modelDefault: "openrouter/auto",
650-
install: withDockerInstall(runner, "OpenClaw", `${DOCKER_IMAGE_PREFIX}openclaw:latest`, () =>
577+
install: () =>
651578
installAgent(
652579
runner,
653580
"openclaw",
654581
`source ~/.bashrc 2>/dev/null; ${NPM_PREFIX_SETUP} && npm install -g \${_NPM_G_FLAGS} openclaw && ` +
655582
"{ grep -qF '.npm-global/bin' ~/.bashrc 2>/dev/null || echo 'export PATH=\"$HOME/.npm-global/bin:$PATH\"' >> ~/.bashrc; } && " +
656583
"{ [ ! -f ~/.zshrc ] || grep -qF '.npm-global/bin' ~/.zshrc 2>/dev/null || echo 'export PATH=\"$HOME/.npm-global/bin:$PATH\"' >> ~/.zshrc; }",
657584
),
658-
),
659585
envVars: (apiKey) => [
660586
`OPENROUTER_API_KEY=${apiKey}`,
661587
`ANTHROPIC_API_KEY=${apiKey}`,
@@ -672,7 +598,6 @@ function createAgents(runner: CloudRunner): Record<string, AgentConfig> {
672598
opencode: {
673599
name: "OpenCode",
674600
cloudInitTier: "minimal",
675-
dockerImage: `${DOCKER_IMAGE_PREFIX}opencode:latest`,
676601
preProvision: promptGithubAuth,
677602
install: () => installAgent(runner, "OpenCode", openCodeInstallCmd()),
678603
envVars: (apiKey) => [
@@ -684,7 +609,6 @@ function createAgents(runner: CloudRunner): Record<string, AgentConfig> {
684609
kilocode: {
685610
name: "Kilo Code",
686611
cloudInitTier: "node",
687-
dockerImage: `${DOCKER_IMAGE_PREFIX}kilocode:latest`,
688612
preProvision: promptGithubAuth,
689613
install: () =>
690614
installAgent(
@@ -705,10 +629,8 @@ function createAgents(runner: CloudRunner): Record<string, AgentConfig> {
705629
zeroclaw: {
706630
name: "ZeroClaw",
707631
cloudInitTier: "minimal",
708-
dockerImage: `${DOCKER_IMAGE_PREFIX}zeroclaw:latest`,
709-
slowInstall: true,
710632
preProvision: promptGithubAuth,
711-
install: withDockerInstall(runner, "ZeroClaw", `${DOCKER_IMAGE_PREFIX}zeroclaw:latest`, async () => {
633+
install: async () => {
712634
// Add swap before building — low-memory instances (e.g., AWS nano 512 MB)
713635
// OOM during Rust compilation if --prefer-prebuilt falls back to source.
714636
await ensureSwapSpace(runner);
@@ -718,7 +640,7 @@ function createAgents(runner: CloudRunner): Record<string, AgentConfig> {
718640
`curl --proto '=https' -LsSf ${ZEROCLAW_INSTALL_URL} | bash -s -- --install-rust --install-system-deps --prefer-prebuilt`,
719641
600, // 10 min: swap-backed compilation is slower than the 5-min default
720642
);
721-
}),
643+
},
722644
envVars: (apiKey) => [
723645
`OPENROUTER_API_KEY=${apiKey}`,
724646
"ZEROCLAW_PROVIDER=openrouter",
@@ -731,7 +653,6 @@ function createAgents(runner: CloudRunner): Record<string, AgentConfig> {
731653
hermes: {
732654
name: "Hermes Agent",
733655
cloudInitTier: "minimal",
734-
dockerImage: `${DOCKER_IMAGE_PREFIX}hermes:latest`,
735656
preProvision: promptGithubAuth,
736657
install: () =>
737658
installAgent(

packages/cli/src/shared/agents.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,6 @@ export interface AgentConfig {
2929
launchCmd: () => string;
3030
/** Cloud-init dependency tier. Defaults to "full" if unset. */
3131
cloudInitTier?: CloudInitTier;
32-
/** Docker image for pre-built agent extraction (e.g. "ghcr.io/openrouterteam/spawn-claude:latest"). */
33-
dockerImage?: string;
34-
/** If true, Docker + image pull are added to cloud-init for faster extraction.
35-
* Only worth it for agents with slow installs (e.g. Rust compilation). */
36-
slowInstall?: boolean;
3732
/** Skip tarball install attempt (e.g., already using snapshot). */
3833
skipTarball?: boolean;
3934
}

0 commit comments

Comments
 (0)