Skip to content

Commit bc42ef3

Browse files
grichaclaude
andauthored
Add eager Docker image pull on agent start (#6)
When the agent starts, it now begins pulling the workspace image in the background. This prevents users from waiting for the image download when creating their first workspace. Features: - Pulls image immediately when agent starts - Retries every 20s if Docker isn't available yet - Non-blocking - agent starts normally while pull happens - Skips if local image already exists 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ea643d7 commit bc42ef3

2 files changed

Lines changed: 80 additions & 0 deletions

File tree

src/agent/run.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { HOST_WORKSPACE_NAME } from '../shared/types';
66
import { DEFAULT_AGENT_PORT } from '../shared/constants';
77
import { WorkspaceManager } from '../workspace/manager';
88
import { containerRunning, getContainerName } from '../docker';
9+
import { startEagerImagePull } from '../docker/eager-pull';
910
import { TerminalWebSocketServer } from '../terminal/websocket';
1011
import { ChatWebSocketServer } from '../chat/websocket';
1112
import { OpencodeWebSocketServer } from '../chat/opencode-websocket';
@@ -204,6 +205,8 @@ export async function startAgent(options: StartAgentOptions = {}): Promise<void>
204205
console.log(`[agent] WebSocket terminal: ws://localhost:${port}/rpc/terminal/:name`);
205206
console.log(`[agent] WebSocket chat (Claude): ws://localhost:${port}/rpc/chat/:name`);
206207
console.log(`[agent] WebSocket chat (OpenCode): ws://localhost:${port}/rpc/opencode/:name`);
208+
209+
startEagerImagePull();
207210
});
208211

209212
const shutdown = () => {

src/docker/eager-pull.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { imageExists, tryPullImage, getDockerVersion } from './index';
2+
import { WORKSPACE_IMAGE_LOCAL, WORKSPACE_IMAGE_REGISTRY } from '../shared/constants';
3+
4+
const RETRY_INTERVAL_MS = 20000;
5+
const MAX_RETRIES = 10;
6+
7+
let pullInProgress = false;
8+
let pullComplete = false;
9+
10+
async function isDockerAvailable(): Promise<boolean> {
11+
try {
12+
await getDockerVersion();
13+
return true;
14+
} catch {
15+
return false;
16+
}
17+
}
18+
19+
async function pullWorkspaceImage(): Promise<boolean> {
20+
const localExists = await imageExists(WORKSPACE_IMAGE_LOCAL);
21+
if (localExists) {
22+
console.log('[agent] Workspace image already available locally');
23+
return true;
24+
}
25+
26+
console.log(`[agent] Pulling workspace image from ${WORKSPACE_IMAGE_REGISTRY}...`);
27+
const pulled = await tryPullImage(WORKSPACE_IMAGE_REGISTRY);
28+
29+
if (pulled) {
30+
console.log('[agent] Workspace image pulled successfully');
31+
return true;
32+
}
33+
34+
console.log('[agent] Failed to pull image - will retry later or build on first workspace create');
35+
return false;
36+
}
37+
38+
export async function startEagerImagePull(): Promise<void> {
39+
if (pullInProgress || pullComplete) {
40+
return;
41+
}
42+
43+
pullInProgress = true;
44+
45+
const attemptPull = async (attempt: number): Promise<void> => {
46+
if (attempt > MAX_RETRIES) {
47+
console.log('[agent] Max retries reached for image pull - giving up background pull');
48+
pullInProgress = false;
49+
return;
50+
}
51+
52+
const dockerAvailable = await isDockerAvailable();
53+
54+
if (!dockerAvailable) {
55+
if (attempt === 1) {
56+
console.log('[agent] Docker not available - will retry in background');
57+
}
58+
setTimeout(() => attemptPull(attempt + 1), RETRY_INTERVAL_MS);
59+
return;
60+
}
61+
62+
const success = await pullWorkspaceImage();
63+
64+
if (success) {
65+
pullComplete = true;
66+
pullInProgress = false;
67+
} else {
68+
setTimeout(() => attemptPull(attempt + 1), RETRY_INTERVAL_MS);
69+
}
70+
};
71+
72+
attemptPull(1);
73+
}
74+
75+
export function isImagePullComplete(): boolean {
76+
return pullComplete;
77+
}

0 commit comments

Comments
 (0)