diff --git a/apps/api/src/api/tool_instances.py b/apps/api/src/api/tool_instances.py index 59f702c..c68cd73 100644 --- a/apps/api/src/api/tool_instances.py +++ b/apps/api/src/api/tool_instances.py @@ -2554,11 +2554,23 @@ async def recreate_tunnel_endpoint( # Also probe from inside the API container directly to the target probe = subprocess.run( - ["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", "--max-time", "5", target_url], + [ + "curl", + "-s", + "-o", + "/dev/null", + "-w", + "%{http_code}", + "--max-time", + "5", + target_url, + ], capture_output=True, text=True, ) - logger.info("Direct probe from API to %s: HTTP %s", target_url, probe.stdout.strip()) + logger.info( + "Direct probe from API to %s: HTTP %s", target_url, probe.stdout.strip() + ) instance.tunnel_id = tunnel_info["container_name"] instance.public_url = tunnel_info["url"] diff --git a/apps/api/src/services/docker.py b/apps/api/src/services/docker.py index 8025ed1..edd965d 100644 --- a/apps/api/src/services/docker.py +++ b/apps/api/src/services/docker.py @@ -179,54 +179,43 @@ def execute_compose_command( def get_container_id(instance_name: str) -> str | None: """Get the container ID for a compose service. - Searches all containers including stopped/exited ones. + Uses exact name matching via docker inspect to avoid substring collisions + with tunnel containers (e.g. tunnel-code-server-... matching code-server-...). Args: - instance_name: The service name in compose + instance_name: The exact container name (case-insensitive for Docker). Returns: - Container ID or None if not found + Container ID or None if not found. """ - # Docker container names are lowercase internally; normalize to ensure match result = subprocess.run( - ["docker", "ps", "-a", "-q", "--filter", f"name={instance_name.lower()}"], + ["docker", "inspect", "-f", "{{.Id}}", instance_name.lower()], capture_output=True, text=True, ) - if result.returncode == 0 and result.stdout.strip(): - return result.stdout.strip().split("\n")[0] + return result.stdout.strip() return None def get_container_name(instance_name: str) -> str | None: """Get the full container name for a compose service. - Searches all containers including stopped/exited ones. + Uses exact name matching via docker inspect to avoid substring collisions. Args: - instance_name: The service name in compose + instance_name: The exact container name (case-insensitive for Docker). Returns: - Container name or None if not found + Container name or None if not found. """ - # Docker container names are lowercase internally; normalize to ensure match result = subprocess.run( - [ - "docker", - "ps", - "-a", - "--format", - "{{.Names}}", - "--filter", - f"name={instance_name.lower()}", - ], + ["docker", "inspect", "-f", "{{.Name}}", instance_name.lower()], capture_output=True, text=True, ) - if result.returncode == 0 and result.stdout.strip(): - return result.stdout.strip().split("\n")[0] + return result.stdout.strip().lstrip("/") return None