fix(smolmachines): docker push fails on Docker Desktop — daemon-side route differs from host loopback
`./cli.py start <agent>` under CLAUDE_BOTTLE_BACKEND=smolmachines died at `docker push localhost:<port>/claude-bottle:<id>` with `Get "http://localhost:<port>/v2/": context deadline exceeded`. Cause: chunk 4c bound the ephemeral registry to `127.0.0.1::5000` and used `localhost:<port>` as the only image-ref hostname. On Docker Desktop the daemon runs inside its own Linux VM — its `localhost` is the VM's loopback, not the host's, so the daemon cannot reach a registry bound to the host's 127.0.0.1. Fix: bind the registry to all interfaces (`-p :5000`) so it's reachable from both sides, and yield two endpoints: - `daemon_endpoint` — `host.docker.internal:<port>` on Docker Desktop (daemon-side hostname for the host VM gateway), `localhost:<port>` on a native Linux daemon that shares the host's network namespace. Used for `docker tag` + `docker push`. - `host_endpoint` — always `localhost:<port>`. Used for `smolvm pack create`, which runs as a host process. The registry stores images by repo+tag, so a push to `host.docker.internal:<port>/cb:<id>` and a pull from `localhost:<port>/cb:<id>` resolve to the same blob — the hostname in a ref is just routing. Detection uses `docker info --format '{{.OperatingSystem}}'`, which returns "Docker Desktop" on macOS/Windows Desktop and the host's OS name on native daemons. Trade-off: all-interface binding briefly publishes the registry on every interface (~5-10s during prepare). The pushed image is built from the public repo Dockerfile (no secrets), the port is random, and the window is short — acceptable for v1 of a personal dev tool. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -184,14 +184,20 @@ def _ensure_smolmachine(image_ref: str) -> Path:
|
||||
a launcher binary at `.smolmachine` plus the sidecar alongside
|
||||
it; the sidecar is the actual artifact).
|
||||
|
||||
Conversion path: `docker build` (the existing layer cache makes
|
||||
no-change rebuilds cheap) → `docker tag` with a
|
||||
`localhost:<port>/...` ref → bring up the ephemeral registry
|
||||
container → `docker push` into it → `smolvm pack create --image
|
||||
<localhost ref>` → tear down the registry. Each pack-create
|
||||
costs several seconds even on a hot cache, so we skip the whole
|
||||
pipeline when the cached sidecar is already on disk for this
|
||||
image ID."""
|
||||
Conversion path: `docker build` (the existing layer cache
|
||||
makes no-change rebuilds cheap) → `docker tag` + `docker push`
|
||||
using the daemon-side endpoint (`host.docker.internal:<port>`
|
||||
on Docker Desktop, `localhost:<port>` on native Linux) →
|
||||
`smolvm pack create --image <host endpoint>` using the
|
||||
host-side endpoint (always `localhost:<port>` — smolvm is a
|
||||
host process) → tear down the registry. The two endpoints
|
||||
route to the same registry container; only the hostname
|
||||
differs because the docker daemon (on Docker Desktop) doesn't
|
||||
share the host's loopback.
|
||||
|
||||
Each pack-create costs several seconds even on a hot cache,
|
||||
so we skip the whole pipeline when the cached sidecar is
|
||||
already on disk for this image ID."""
|
||||
_SMOLMACHINE_CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
docker_mod.build_image(image_ref, _REPO_DIR)
|
||||
# `sha256:abcd...` -> `abcd...` first 16 chars: short enough to
|
||||
@@ -202,9 +208,10 @@ def _ensure_smolmachine(image_ref: str) -> Path:
|
||||
sidecar = _SMOLMACHINE_CACHE_DIR / f"{digest}.smolmachine.smolmachine"
|
||||
if sidecar.is_file():
|
||||
return sidecar
|
||||
with ephemeral_registry() as port:
|
||||
local_ref = f"localhost:{port}/claude-bottle:{digest}"
|
||||
docker_mod.tag(image_ref, local_ref)
|
||||
docker_mod.push(local_ref)
|
||||
_smolvm.pack_create(local_ref, binary)
|
||||
with ephemeral_registry() as endpoints:
|
||||
push_ref = f"{endpoints.daemon_endpoint}/claude-bottle:{digest}"
|
||||
pack_ref = f"{endpoints.host_endpoint}/claude-bottle:{digest}"
|
||||
docker_mod.tag(image_ref, push_ref)
|
||||
docker_mod.push(push_ref)
|
||||
_smolvm.pack_create(pack_ref, binary)
|
||||
return sidecar
|
||||
|
||||
Reference in New Issue
Block a user