Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions components/gateway-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ export class GatewayImage extends pulumi.ComponentResource {
);

// The resource that downstream depends on — either a single Image or an Index.
// imageDigestTrigger is the registry manifest digest — changes on every push,
// used to force RemoteImage replacement (triggers, not pullTriggers).
// imageDigestTrigger is the build output digest — changes on every rebuild,
// used alongside the registry manifest digest in RemoteImage pullTriggers.
let image: pulumi.Resource;
let imageDigestTrigger: pulumi.Output<string>;

Expand Down Expand Up @@ -339,17 +339,19 @@ export class GatewayImage extends pulumi.ComponentResource {
return match.sha256Digest;
});

// pullTriggers with registry digest as source of truth. When the digest
// changes, Pulumi replaces the resource (destroy + create). forceRemove
// ensures the destroy step removes the image even when running containers
// reference it — without this, findImage() finds the stale local tag and
// skips the pull entirely (kreuzwerker/terraform-provider-docker behavior).
// Two triggers ensure the pull always fires:
// 1. pullDigest (registry manifest) — catches out-of-band pushes and
// state desync (registry is the source of truth)
// 2. imageDigestTrigger (build output) — catches same-deploy rebuilds
// where the image hasn't been pushed to the registry yet at plan time
// forceRemove ensures destroy removes the image even when running
// containers reference it (findImage() short-circuit workaround).
const pulledImage = new docker.RemoteImage(
`${name}-pull`,
{
name: pullTag,
platform: args.platform,
pullTriggers: [pullDigest],
pullTriggers: [pullDigest, imageDigestTrigger],
forceRemove: true,
},
{
Expand Down
26 changes: 23 additions & 3 deletions templates/dockerfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,29 @@ RUN mkdir -p "\${OPENCLAW_CONFIG_DIR}" "\${OPENCLAW_WORKSPACE_DIR}" && \\
RUN SHARP_IGNORE_GLOBAL_LIBVIPS=1 NODE_OPTIONS=--max-old-space-size=2048 \\
npm install -g --no-fund --no-audit "openclaw@\${OPENCLAW_VERSION}"

# CLI symlink for consistent access across users
RUN ln -sf "$(npm root -g)/openclaw/dist/entry.js" /usr/local/bin/openclaw && \\
chmod 755 "$(npm root -g)/openclaw/dist/entry.js"
# WORKAROUND: Route CLI commands through Bun instead of Node.
#
# OpenClaw's bundled JS (~69MB) takes 7+ seconds to parse on Skylake Xeon CPUs
# under Node's V8 engine. The gateway enforces a 3s handshake timeout on WS
# connections (DEFAULT_HANDSHAKE_TIMEOUT_MS in server-constants.ts). CLI commands
# open a WebSocket to the gateway, but V8's synchronous startup blocks the event
# loop so the CLI can't respond to the WS challenge before the 3s timeout expires.
#
# Bun's JavaScriptCore engine parses the same bundles in ~150ms, avoiding the issue.
# Both the gateway server and CLI invocations run through this Bun wrapper (the
# Dockerfile CMD resolves to /usr/local/bin/openclaw, which execs bun). This is
# acceptable — Bun's Node.js compatibility covers OpenClaw's runtime needs.
#
# Upstream refs:
# https://github.com/openclaw/openclaw/issues/45560
# https://github.com/openclaw/openclaw/issues/45940
#
# Remove this workaround when upstream bumps the handshake timeout or optimizes
# CLI startup.
RUN OPENCLAW_ENTRY="$(npm root -g)/openclaw/dist/entry.js" && \\
echo '#!/bin/sh' > /usr/local/bin/openclaw && \\
echo "exec bun \\"$OPENCLAW_ENTRY\\" \\"\\$@\\"" >> /usr/local/bin/openclaw && \\
chmod 755 /usr/local/bin/openclaw

# Optional: install Chromium + Xvfb for browser automation.
${renderBrowserBlock(opts.installBrowser ?? false)}
Expand Down
4 changes: 2 additions & 2 deletions tests/templates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,10 @@ describe("renderDockerfile", () => {
expect(df).toContain("chmod 700 /usr/local/bin/firewall-bypass");
});

it("creates CLI symlink", () => {
it("creates CLI wrapper", () => {
const df = renderDockerfile(defaultOpts);
expect(df).toContain("ln -sf");
expect(df).toContain("/usr/local/bin/openclaw");
expect(df).toContain("exec bun");
});

it("does not install Tailscale CLI (handled by sidecar container)", () => {
Expand Down
Loading