Skip to content

fix(workspace): publish Docker ports on loopback - #5209

Draft
neubig wants to merge 2 commits into
mainfrom
fix/docker-workspace-loopback-ports
Draft

neubig wants to merge 2 commits into
mainfrom
fix/docker-workspace-loopback-ports

Conversation

@neubig

@neubig neubig commented Sep 21, 2026

Copy link
Copy Markdown
Member

HUMAN:

Tested a full canvas setup through the agent.


AGENT:

Why

DockerWorkspace is a locally managed workspace and its SDK client connects through 127.0.0.1, but Docker currently publishes its API and optional VS Code ports on every host interface. Aligning the publication address with the client address keeps the default networking behavior local to the machine.

When a session API key is forwarded into the container, the workspace client also needs to retain that key so its HTTP and WebSocket requests use the same configuration.

Summary

  • Publish DockerWorkspace API and optional VS Code ports on host loopback.
  • Keep the agent server listening on the container interface for Docker port forwarding.
  • Propagate the configured session API key to the DockerWorkspace client.
  • Add focused regression coverage for port mappings and key precedence.

Issue Number

OSS-10566

How to Test

Focused checks:

uv run pytest tests/workspace/test_docker_workspace.py -q
uv run ruff check openhands-workspace/openhands/workspace/docker/workspace.py tests/workspace/test_docker_workspace.py
uv run ruff format --check openhands-workspace/openhands/workspace/docker/workspace.py tests/workspace/test_docker_workspace.py

Result: 22 tests passed; Ruff lint and formatting checks passed. The test suite still reports its pre-existing DockerWorkspace.__del__ cleanup warning in unrelated lifecycle tests.

End-to-end Docker smoke test:

uv run python - <<'PYCODE'
import subprocess
from openhands.workspace import DockerWorkspace

with DockerWorkspace(
    server_image="ghcr.io/openhands/agent-server:latest-python",
    detach_logs=False,
) as workspace:
    result = workspace.execute_command("printf docker-workspace-ok")
    binding = subprocess.run(
        ["docker", "port", workspace._container_id, "8000/tcp"],
        check=True,
        capture_output=True,
        text=True,
    ).stdout.strip()
    print(f"BINDING={binding}")
    print(f"COMMAND_OUTPUT={result.stdout.strip()}")
PYCODE

Observed output:

BINDING=127.0.0.1:36957
COMMAND_OUTPUT=docker-workspace-ok

The generated launch command retained --host 0.0.0.0 --port 8000 inside the container and used -p 127.0.0.1:36957:8000 on the Docker host.

Agent Canvas end-to-end validation:

Canvas UI: http://localhost:8100
Agent server: http://127.0.0.1:18110
Conversation runtime: OH_CONVERSATION_RUNTIME=docker
Conversation: ce2ce5a0-2d7a-469b-8a1c-2c7dbdef010b
Terminal command: printf canvas-docker-e2e > canvas-docker-result.txt && cat canvas-docker-result.txt
Terminal exit code: 0
Terminal hostname: e45b6f94c004
Terminal working directory: /workspace
Final conversation status: finished
Rendered UI result count: 1

The conversation ran through the Canvas API and UI, executed the terminal action in its Docker conversation runtime, wrote the mounted workspace file, returned canvas-docker-e2e, and rendered the completed result on the conversation page.

Video/Screenshots

Successful Agent Canvas conversation using the Docker conversation runtime:

Successful Agent Canvas Docker workflow

Design Doc

Not needed for this focused change. It follows the existing loopback publication pattern used by the conversation-container runtime.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

The agent process must continue listening on 0.0.0.0 inside the container so Docker's bridge forwarding can reach it. The loopback restriction belongs on the host-side -p mapping.

This pull request was created by an AI agent (OpenHands) on behalf of the user.


🐳 Agent Server images for this PR — GHCR package, pull/run commands, and all pushed tags (click to expand)

GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python-slim amd64, arm64 python-node-runtime Link
python-minimal amd64, arm64 python-node-runtime Link
python amd64, arm64 python-node-runtime Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:90b9d8c-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-90b9d8c-python \
  ghcr.io/openhands/agent-server:90b9d8c-python

All tags pushed for this build

ghcr.io/openhands/agent-server:90b9d8c-golang-amd64
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-golang-amd64
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-golang-amd64
ghcr.io/openhands/agent-server:90b9d8c-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:90b9d8c-golang-arm64
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-golang-arm64
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-golang-arm64
ghcr.io/openhands/agent-server:90b9d8c-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:90b9d8c-java-amd64
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-java-amd64
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-java-amd64
ghcr.io/openhands/agent-server:90b9d8c-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:90b9d8c-java-arm64
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-java-arm64
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-java-arm64
ghcr.io/openhands/agent-server:90b9d8c-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:90b9d8c-python-amd64
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-python-amd64
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-python-amd64
ghcr.io/openhands/agent-server:90b9d8c-python-node-runtime-amd64
ghcr.io/openhands/agent-server:90b9d8c-python-arm64
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-python-arm64
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-python-arm64
ghcr.io/openhands/agent-server:90b9d8c-python-node-runtime-arm64
ghcr.io/openhands/agent-server:90b9d8c-python-minimal-amd64
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-python-minimal-amd64
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-python-minimal-amd64
ghcr.io/openhands/agent-server:90b9d8c-python-node-runtime-minimal-amd64
ghcr.io/openhands/agent-server:90b9d8c-python-minimal-arm64
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-python-minimal-arm64
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-python-minimal-arm64
ghcr.io/openhands/agent-server:90b9d8c-python-node-runtime-minimal-arm64
ghcr.io/openhands/agent-server:90b9d8c-python-slim-amd64
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-python-slim-amd64
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-python-slim-amd64
ghcr.io/openhands/agent-server:90b9d8c-python-node-runtime-slim-amd64
ghcr.io/openhands/agent-server:90b9d8c-python-slim-arm64
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-python-slim-arm64
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-python-slim-arm64
ghcr.io/openhands/agent-server:90b9d8c-python-node-runtime-slim-arm64
ghcr.io/openhands/agent-server:90b9d8c-golang
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-golang
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-golang
ghcr.io/openhands/agent-server:90b9d8c-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:90b9d8c-java
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-java
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-java
ghcr.io/openhands/agent-server:90b9d8c-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:90b9d8c-python-minimal
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-python-minimal
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-python-minimal
ghcr.io/openhands/agent-server:90b9d8c-python-node-runtime-minimal
ghcr.io/openhands/agent-server:90b9d8c-python-slim
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-python-slim
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-python-slim
ghcr.io/openhands/agent-server:90b9d8c-python-node-runtime-slim
ghcr.io/openhands/agent-server:90b9d8c-python
ghcr.io/openhands/agent-server:90b9d8c0bd23608dbeda246dd0d591b4931e7860-python
ghcr.io/openhands/agent-server:fix-docker-workspace-loopback-ports-python
ghcr.io/openhands/agent-server:90b9d8c-python-node-runtime

About Multi-Architecture Support

  • Each variant tag (e.g., 90b9d8c-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., 90b9d8c-python-amd64) are also available if needed

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

github-actions Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result:PASSED

Action log

@github-actions

github-actions Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-workspace/openhands/workspace/docker
   workspace.py1924477%30–32, 50, 148, 168, 192, 196, 203, 215–216, 228, 255, 287, 296, 303–304, 308–309, 313, 316–317, 319–325, 328–329, 338–340, 344–346, 350, 354, 360–361, 384, 404, 421
TOTAL429472169149% 

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

Copy link
Copy Markdown
Contributor

📁 PR Artifacts Notice

This PR contains a .pr/ directory with temporary PR-specific documents. The directory will be automatically removed when the PR is approved.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants