From bc8b995dd3ceea02cc6ff62023398d479a0e4a6e Mon Sep 17 00:00:00 2001 From: Joe O'Connor <48067162+Joeoc2001@users.noreply.github.com> Date: Wed, 20 Aug 2025 20:52:31 +0200 Subject: [PATCH] Add additional networks (#9566) Co-authored-by: Engel Nyst --- docs/usage/runtimes/docker.mdx | 25 +++++++++++++++++++ openhands/core/config/sandbox_config.py | 2 ++ .../runtime/impl/docker/docker_runtime.py | 17 +++++++++++++ 3 files changed, 44 insertions(+) diff --git a/docs/usage/runtimes/docker.mdx b/docs/usage/runtimes/docker.mdx index 609f3134b2..77b6414d51 100644 --- a/docs/usage/runtimes/docker.mdx +++ b/docs/usage/runtimes/docker.mdx @@ -130,3 +130,28 @@ docker run # ... \ **Docker Desktop Required**: Network isolation features, including custom networks and `host.docker.internal` routing, require Docker Desktop. Docker Engine alone does not support these features on localhost across custom networks. If you're using Docker Engine without Docker Desktop, network isolation may not work as expected. + +### Sidecar Containers + +If you want to run sidecar containers to the sandbox 'runner' containers without exposing the sandbox containers to the host network, you can use the `SANDBOX_ADDITIONAL_NETWORKS` environment variable to specify additional Docker network names that should be added to the sandbox containers. + +```bash +docker network create openhands-sccache + +docker run -d \ + --hostname openhandsredis \ + --network openhands-sccache \ + redis + +docker run # ... + -e SANDBOX_ADDITIONAL_NETWORKS='["openhands-sccache"]' \ + # ... +``` + +Then all sandbox instances will have to access a shared redis instance at `openhandsredis:6379`. + +#### Docker Compose gotcha + +Note that Docker Compose adds a prefix (a scope) by default to created networks, which is not taken into account by the additional networks config. Therefore when using docker compose you have to either: +- specify a network name via the `name` field to remove the scoping (https://docs.docker.com/reference/compose-file/networks/#name) +- or provide the scope within the given config (e.g. `SANDBOX_ADDITIONAL_NETWORKS: '["myscope_openhands-sccache"]'` where `myscope` is the docker-compose assigned prefix). \ No newline at end of file diff --git a/openhands/core/config/sandbox_config.py b/openhands/core/config/sandbox_config.py index 002d90f92f..a76f132adf 100644 --- a/openhands/core/config/sandbox_config.py +++ b/openhands/core/config/sandbox_config.py @@ -18,6 +18,7 @@ class SandboxConfig(BaseModel): remote_runtime_enable_retries: Whether to enable retries (on recoverable errors like requests.ConnectionError) for the remote runtime API requests. enable_auto_lint: Whether to enable auto-lint. use_host_network: Whether to use the host network. + additional_networks: A list of additional Docker networks to connect to runtime_binding_address: The binding address for the runtime ports. It specifies which network interface on the host machine Docker should bind the runtime ports to. initialize_plugins: Whether to initialize plugins. force_rebuild_runtime: Whether to force rebuild the runtime image. @@ -65,6 +66,7 @@ class SandboxConfig(BaseModel): default=False ) # once enabled, OpenHands would lint files after editing use_host_network: bool = Field(default=False) + additional_networks: list[str] = Field(default=[]) runtime_binding_address: str = Field(default='0.0.0.0') runtime_extra_build_args: list[str] | None = Field(default=None) initialize_plugins: bool = Field(default=True) diff --git a/openhands/runtime/impl/docker/docker_runtime.py b/openhands/runtime/impl/docker/docker_runtime.py index d3da289b17..0dfc1e8946 100644 --- a/openhands/runtime/impl/docker/docker_runtime.py +++ b/openhands/runtime/impl/docker/docker_runtime.py @@ -213,6 +213,23 @@ class DockerRuntime(ActionExecutionClient): self.set_runtime_status(RuntimeStatus.READY) self._runtime_initialized = True + for network_name in self.config.sandbox.additional_networks: + try: + network = self.docker_client.networks.get(network_name) + if self.container is not None: + network.connect(self.container) + else: + self.log( + 'warning', + f'Container not available to connect to network {network_name}', + ) + except Exception as e: + self.log( + 'error', + f'Error: Failed to connect instance {self.container_name} to network {network_name}', + ) + self.log('error', str(e)) + def maybe_build_runtime_container_image(self): if self.runtime_container_image is None: if self.base_container_image is None: