fix: run tunnel containers on backend network with container name DNS
The host-network tunnel approach had issues because localhost inside the tunnel container wasn't reaching the host-published ports correctly. This reverts to running cloudflared as a Docker container on the 'backend' network, where Docker DNS resolves container names reliably. The tunnel connects to http://{container_name}:{container_port}. - apps/api/src/services/tunnel.py: use --network backend instead of host - apps/api/src/api/tool_instances.py: pass container_port (default_port) instead of published_port (host port) to tunnel functions Quality gates: ruff clean
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
"""Clean tunnel service using host-network cloudflared containers.
|
||||
"""Clean tunnel service using cloudflared containers on the backend network.
|
||||
|
||||
Design:
|
||||
- Each tunnel runs as a Docker container on the host network.
|
||||
- cloudflared connects to localhost:{published_port}, leveraging Docker's
|
||||
port forwarding. No container name resolution is required.
|
||||
- Tunnels are named predictably (tunnel-{instance_name}) for start/stop.
|
||||
- Each tunnel runs as a Docker container on the same 'backend' network as the API.
|
||||
- cloudflared connects to the tool container by its Docker Compose service name
|
||||
(e.g. http://code-server-headquarter-34837cd3:8443).
|
||||
- This avoids host port conflicts and DNS resolution issues.
|
||||
"""
|
||||
|
||||
import logging
|
||||
@@ -15,6 +15,7 @@ from typing import Any
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TUNNEL_IMAGE = "cloudflare/cloudflared:latest"
|
||||
TUNNEL_NETWORK = "backend"
|
||||
|
||||
|
||||
def _tunnel_container_name(instance_name: str) -> str:
|
||||
@@ -79,13 +80,13 @@ def _get_container_exit_code(tunnel_name: str) -> int | None:
|
||||
|
||||
|
||||
def start_tunnel(
|
||||
instance_name: str, published_port: int, timeout: int = 30
|
||||
instance_name: str, container_port: int, timeout: int = 30
|
||||
) -> dict[str, str]:
|
||||
"""Start a temporary Cloudflare tunnel for an instance.
|
||||
|
||||
Args:
|
||||
instance_name: The tool instance name (used to derive tunnel container name).
|
||||
published_port: The host port Docker forwards to the container.
|
||||
instance_name: The tool instance name (used for tunnel naming).
|
||||
container_port: The port the tool container listens on internally.
|
||||
timeout: Seconds to wait for the tunnel URL.
|
||||
|
||||
Returns:
|
||||
@@ -96,19 +97,22 @@ def start_tunnel(
|
||||
tunnel_name = _tunnel_container_name(instance_name)
|
||||
_cleanup_stale_tunnel(tunnel_name)
|
||||
|
||||
# Target the tool container by name on the backend network
|
||||
target_url = f"http://{instance_name.lower()}:{container_port}"
|
||||
|
||||
cmd = [
|
||||
"docker",
|
||||
"run",
|
||||
"-d",
|
||||
"--network",
|
||||
"host",
|
||||
TUNNEL_NETWORK,
|
||||
"--name",
|
||||
tunnel_name,
|
||||
TUNNEL_IMAGE,
|
||||
"tunnel",
|
||||
"--no-autoupdate",
|
||||
"--url",
|
||||
f"http://localhost:{published_port}",
|
||||
target_url,
|
||||
]
|
||||
|
||||
logger.debug("Running: %s", " ".join(cmd))
|
||||
@@ -148,7 +152,6 @@ def start_tunnel(
|
||||
__import__("time").sleep(0.5)
|
||||
|
||||
if not url:
|
||||
# Capture final state for debugging
|
||||
stdout, stderr = _get_container_logs(tunnel_name)
|
||||
combined_logs = stdout + "\n" + stderr
|
||||
exit_code = _get_container_exit_code(tunnel_name)
|
||||
@@ -160,10 +163,10 @@ def start_tunnel(
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"Tunnel %s started for %s on port %d → %s",
|
||||
"Tunnel %s started for %s → %s (%s)",
|
||||
tunnel_name,
|
||||
instance_name,
|
||||
published_port,
|
||||
target_url,
|
||||
url,
|
||||
)
|
||||
return {"url": url, "container_name": tunnel_name}
|
||||
@@ -176,10 +179,10 @@ def stop_tunnel(instance_name: str) -> None:
|
||||
logger.debug("Stopped and removed tunnel container %s", tunnel_name)
|
||||
|
||||
|
||||
def recreate_tunnel(instance_name: str, published_port: int) -> dict[str, str]:
|
||||
def recreate_tunnel(instance_name: str, container_port: int) -> dict[str, str]:
|
||||
"""Recreate a tunnel for an instance."""
|
||||
stop_tunnel(instance_name)
|
||||
return start_tunnel(instance_name, published_port)
|
||||
return start_tunnel(instance_name, container_port)
|
||||
|
||||
|
||||
def check_tunnel_health(url: str, timeout: int = 10) -> dict[str, Any]:
|
||||
|
||||
Reference in New Issue
Block a user