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
3 changes: 3 additions & 0 deletions architecture/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,9 @@ table.
by the operator or packaging layer.
- Compute runtimes own the mechanics of starting workloads and injecting
callback configuration.
- Docker-backed local gateways use Docker's `host-gateway` callback alias on
macOS and Docker Desktop-style runtimes. Native Linux Docker may expose an
additional bridge-gateway listener because the host can bind that bridge IP.
- Gateway restarts recover persisted objects from storage, but live relay
streams must be re-established by supervisors.
- User-facing behavior changes must update published docs in `docs/`; this file
Expand Down
22 changes: 13 additions & 9 deletions crates/openshell-driver-docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ The gateway runs as a host process. The Docker driver creates one container per
sandbox and starts the `openshell-sandbox` supervisor inside that container. The
supervisor then creates the nested sandbox namespace for the agent process.

Docker containers currently use host networking. This lets a supervisor reach a
gateway bound to `127.0.0.1` without requiring a separate bridge listener, NAT
rule, or userland proxy. The container also receives
`host.openshell.internal -> 127.0.0.1` so local host services have a stable
OpenShell-owned name.
Docker containers join an OpenShell-managed bridge network. The driver injects
`host.openshell.internal` and `host.docker.internal` so supervisors have stable
names for reaching the gateway host. On Docker Desktop, Colima, Rancher
Desktop, OrbStack, and macOS-hosted gateways, those names use Docker's
`host-gateway` alias. On native Linux Docker, the gateway also binds the bridge
gateway IP so containers can call back to the host process.

## Container Contract

Expand All @@ -26,7 +27,7 @@ contract:
| Setting | Purpose |
|---|---|
| `user = "0"` | The supervisor needs root inside the container to prepare namespaces, mounts, Landlock, and seccomp. |
| `network_mode = "host"` | Lets the supervisor call back to loopback gateway endpoints. |
| `network_mode = openshell` | Places the supervisor on the managed Docker bridge network. |
| `cap_add` | Grants supervisor-only capabilities required for namespace setup and process inspection. |
| `apparmor=unconfined` | Avoids Docker's default profile blocking required mount operations. |
| `restart_policy = unless-stopped` | Keeps managed sandboxes resumable across daemon or gateway restarts. |
Expand All @@ -51,9 +52,12 @@ into the binary at compile time. The default Docker supervisor image is not

## Callback and TLS

`OPENSHELL_ENDPOINT` is injected from the gateway's configured gRPC endpoint
without rewriting. Because the container uses host networking, loopback
endpoints such as `http://127.0.0.1:8080` resolve to the host gateway.
`OPENSHELL_ENDPOINT` is injected from the gateway's configured gRPC endpoint.
When no endpoint is configured, the driver uses
`host.openshell.internal:<gateway-port>` with the appropriate HTTP or HTTPS
scheme. Set `host_gateway_ip` only when the host has an explicit, locally
assigned address that containers should use for callbacks; package-managed
macOS gateways should leave it unset.

For HTTPS endpoints, the server certificate must include the endpoint host as a
subject alternative name. Docker sandboxes also need the client TLS bundle
Expand Down
22 changes: 21 additions & 1 deletion crates/openshell-driver-docker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,22 @@ fn docker_gateway_route(
bridge_gateway_ip: IpAddr,
port: u16,
host_gateway_ip: Option<IpAddr>,
) -> DockerGatewayRoute {
docker_gateway_route_for_host(
info,
bridge_gateway_ip,
port,
host_gateway_ip,
host_runtime_requires_host_gateway_alias(),
)
}

fn docker_gateway_route_for_host(
info: &SystemInfo,
bridge_gateway_ip: IpAddr,
port: u16,
host_gateway_ip: Option<IpAddr>,
host_requires_host_gateway_alias: bool,
) -> DockerGatewayRoute {
if let Some(host_alias_ip) = host_gateway_ip {
return DockerGatewayRoute::Bridge {
Expand All @@ -1259,7 +1275,7 @@ fn docker_gateway_route(
};
}

if uses_host_gateway_alias(info) {
if host_requires_host_gateway_alias || uses_host_gateway_alias(info) {
DockerGatewayRoute::HostGateway
} else {
DockerGatewayRoute::Bridge {
Expand All @@ -1269,6 +1285,10 @@ fn docker_gateway_route(
}
}

fn host_runtime_requires_host_gateway_alias() -> bool {
cfg!(target_os = "macos")
}

/// Detect Docker Desktop and behaviourally compatible runtimes - Colima,
/// Lima, Rancher Desktop, and `OrbStack` - that share Docker Desktop's routing
/// constraint: the bridge gateway IP is reachable from inside containers but
Expand Down
22 changes: 21 additions & 1 deletion crates/openshell-driver-docker/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,12 @@ fn docker_gateway_route_uses_bridge_gateway_for_linux_docker() {
..Default::default()
};

let route = docker_gateway_route(
let route = docker_gateway_route_for_host(
&info,
IpAddr::V4(Ipv4Addr::new(172, 18, 0, 1)),
DEFAULT_SERVER_PORT,
None,
false,
);

assert_eq!(
Expand All @@ -291,6 +292,25 @@ fn docker_gateway_route_uses_bridge_gateway_for_linux_docker() {
);
}

#[test]
fn docker_gateway_route_uses_host_gateway_when_host_runtime_requires_it() {
let info = SystemInfo {
operating_system: Some("Ubuntu 24.04 LTS".to_string()),
..Default::default()
};

assert_eq!(
docker_gateway_route_for_host(
&info,
IpAddr::V4(Ipv4Addr::new(10, 89, 10, 1)),
DEFAULT_SERVER_PORT,
None,
true,
),
DockerGatewayRoute::HostGateway
);
}

#[test]
fn docker_gateway_route_prefers_configured_host_gateway_ip() {
let info = SystemInfo {
Expand Down
Loading