Skip to content

fix(container-gateway): find the current docker context without a docker CLI - #1966

Merged
potiuk merged 2 commits into
apache:mainfrom
paulk-asert:fix/container-gateway-docker-context
Nov 2, 2026
Merged

potiuk merged 2 commits into
apache:mainfrom
paulk-asert:fix/container-gateway-docker-context

Conversation

@paulk-asert

Copy link
Copy Markdown
Contributor

Summary

  • On macOS the gateway found its docker backend with docker context inspect, but the hook that starts it pins PATH=/usr/bin:/bin:/usr/sbin:/sbin, where neither a Homebrew nor a Docker Desktop CLI lives. Discovery silently fell back to ~/.docker/run/docker.sock; with Colima active and a socket file left behind by an earlier Docker Desktop, the gateway served that dead socket and every call failed with backend /Users/<you>/.docker/run/docker.sock is unreachable ([Errno 61] Connection refused).
  • The current context is now read from the docker CLI's own files (currentContext in ~/.docker/config.json, the endpoint from the context store keyed by the SHA-256 of the context name), so no docker binary is needed and DOCKER_HOST (which names the gateway's own socket in a session) cannot leak in.
  • A candidate socket is used only when something accepts a connection on it (socket_answers), so a stale socket file no longer wins.

Type of change

  • Python package (tools/*/ with pyproject.toml)
  • Documentation (docs/, README.md, CONTRIBUTING.md)

Test plan

  • prek run on the changed files passes, except lychee, skipped locally (needs a Rust toolchain download; CI runs it). isolated-setup-fingerprint regenerated isolated_fingerprint.py, included.
  • uv run pytest / ruff check / ruff format --check / mypy (via the workspace hooks) pass for tools/container-gateway
  • New tests in test_backends.py: a non-default context (Colima) is read from the context store with no docker CLI called; a context whose socket does not answer falls back; default, missing, metadata-less and tcp:// contexts and a non-JSON config.json fall back to the Desktop socket; socket_answers rejects a regular file and a missing path, and (where Unix bind is allowed; skipped in the agent sandbox as the existing bind tests are) accepts a live listener but not the socket file it leaves behind
  • socket_answers checked by hand against a running gateway socket (True) and a missing path (False)
  • Found and verified end to end on macOS with Colima and a Homebrew docker: with the old discovery the gateway logged backend docker at ~/.docker/run/docker.sock and every call was refused; pointing that path at Colima's socket made docker version report the Colima server through the gateway

RFC-AI-0004 compliance

  • Sandbox — no new unrestricted host access. The gateway (outside the sandbox) reads two more files under ~/.docker; the sandbox's own ~/.docker read denial is unchanged.

Linked issues

None.

Notes for reviewers (optional)

  • Design point: ~/.docker/config.json. The README and spec said the gateway never reads this file. It now reads it, but takes only currentContext and never auths, credsStore or credHelpers; both docs are restated accordingly. The old code already depended on that file indirectly, since docker context inspect reads it. The alternative considered was running docker from fixed trusted locations (/opt/homebrew/bin, /usr/local/bin), which would need the hook's writable-root checks re-implemented in Python, and would still have to scrub DOCKER_HOST/DOCKER_CONTEXT from its environment.
  • DOCKER_CONTEXT and DOCKER_CONFIG are deliberately not honoured: like DOCKER_HOST, they can come from the session's settings environment.
  • socket_answers uses a 1 s connect timeout per candidate; discovery runs once at gateway start.

🤖 Generated with Claude Code

…ker CLI

On macOS the gateway took its docker backend from `docker context inspect`.
The hook that starts it pins PATH to /usr/bin:/bin:/usr/sbin:/sbin, where
neither a Homebrew nor a Docker Desktop CLI lives, so the call failed
silently and discovery fell back to ~/.docker/run/docker.sock. With Colima
(or any other non-default context) and a socket file left behind by an
earlier Docker Desktop, the gateway served that dead socket and every
request failed with "backend ... is unreachable ([Errno 61] Connection
refused)".

Read the current context from the CLI's own files instead: currentContext
from ~/.docker/config.json and the endpoint from the context store, keyed
by the SHA-256 of the context name. Only that one key is taken from
config.json, never auths, credsStore or credHelpers; the README and spec
now say so rather than that the file is never read. Not running `docker`
also keeps DOCKER_HOST out of it, which in a session names the gateway's
own socket.

A candidate socket is used only when something accepts a connection on
it, so a stale socket file no longer wins over a later candidate.

Generated-by: Claude Code (Opus 5.5)
@github-actions github-actions Bot added family:setup setup-* skills family:tools tools/* family:docs Docs, MISSION.md, READMEs capability:platform Framework / agent substrate skills (install, verify, doctor, override, status, setup bootstrap) substrate:sandbox Tool substrate: agent isolation, egress control, settings audit family:dev Repo dev: .asf.yaml, spec-loop specs, Node manifests, .claude/settings.json labels Nov 2, 2026
…rder

- Read the docker CLI's config.json and context store from $DOCKER_CONFIG
  when it is set, as the CLI does, else ~/.docker; with a test.
- Say in the troubleshooting notes that discovery runs once at gateway
  start, so a runtime started after the session is not picked up until the
  gateway restarts, now that a socket must answer to be used.
- Restamp the isolated-setup fingerprint for the gateway change.

Generated-by: Claude Opus 5
@potiuk
potiuk merged commit 1dcbba2 into apache:main Nov 2, 2026
12 checks passed

@potiuk potiuk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @paulk-asert — reading the current context from the CLI's own files instead of a docker binary the hook's PATH never had is the right fix, it keeps away from auths / credsStore / credHelpers, and the tests cover the context store, the silent socket and every fallback well.

Two small points I fixed on your branch rather than ask for another round (755335d):

  • docs/setup/sandbox-troubleshooting.md: discovery runs once at gateway start, and now that a socket has to answer, a Docker Desktop or Colima started after the session is not picked up until the gateway restarts (before, the leftover socket file was served and worked once the daemon came up). The Notes now say to start the runtime first and what the log shows otherwise.
  • tools/container-gateway/src/container_gateway/backends.py: the context files are read from $DOCKER_CONFIG when it is set, as the docker CLI does, else ~/.docker; new test test_docker_current_context_read_from_docker_config. The isolated-setup fingerprint is restamped for it.

Heads-up: #1967 edits the same Notes list, so whichever merges second may need a quick rebase.


This review was drafted by an AI-assisted tool and
confirmed by an Apache Magpie maintainer. The maintainer
approving this PR has read the findings and signed off. If
something feels off, please reply on the PR and a maintainer
will follow up.

More on how Apache Magpie handles maintainer review:
Contributing guide.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

capability:platform Framework / agent substrate skills (install, verify, doctor, override, status, setup bootstrap) family:dev Repo dev: .asf.yaml, spec-loop specs, Node manifests, .claude/settings.json family:docs Docs, MISSION.md, READMEs family:setup setup-* skills family:tools tools/* substrate:sandbox Tool substrate: agent isolation, egress control, settings audit

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants