diff --git a/apps/api/src/api/tool_instances.py b/apps/api/src/api/tool_instances.py index c68cd73..92a0cf1 100644 --- a/apps/api/src/api/tool_instances.py +++ b/apps/api/src/api/tool_instances.py @@ -2470,26 +2470,16 @@ async def recreate_tunnel_endpoint( tool_type.default_port, ) - # Find the tool container — try stored ID first, then fall back to exact name + # Find the tool container — try stored ID first, then fall back to name lookup tool_container_id = instance.container_id if tool_container_id: logger.info("Using stored container_id: %s", tool_container_id) else: - # Use exact name match via inspect to avoid substring collisions with tunnel containers - inspect_result = subprocess.run( - ["docker", "inspect", "-f", "{{.Id}}", expected_name], - capture_output=True, - text=True, - ) - if inspect_result.returncode == 0 and inspect_result.stdout.strip(): - tool_container_id = inspect_result.stdout.strip() - logger.info("Found container by exact name: %s", tool_container_id) + tool_container_id = get_container_id(expected_name) + if tool_container_id: + logger.info("Found container by name: %s", tool_container_id) else: - logger.error( - "Container %s not found via docker inspect: %s", - expected_name, - inspect_result.stderr, - ) + logger.error("Container %s not found", expected_name) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Could not find running container for this instance", diff --git a/apps/api/src/services/docker.py b/apps/api/src/services/docker.py index edd965d..5e5a606 100644 --- a/apps/api/src/services/docker.py +++ b/apps/api/src/services/docker.py @@ -179,22 +179,41 @@ def execute_compose_command( def get_container_id(instance_name: str) -> str | None: """Get the container ID for a compose service. - Uses exact name matching via docker inspect to avoid substring collisions - with tunnel containers (e.g. tunnel-code-server-... matching code-server-...). + Uses exact name matching to avoid substring collisions with tunnel + containers (e.g. tunnel-code-server-... matching code-server-...). + Falls back to case-insensitive matching since Docker DNS is case- + insensitive but docker inspect is case-sensitive. Args: - instance_name: The exact container name (case-insensitive for Docker). + instance_name: The expected container name. Returns: Container ID or None if not found. """ + expected = instance_name.lower() + + # Fast path: exact match via docker inspect result = subprocess.run( - ["docker", "inspect", "-f", "{{.Id}}", instance_name.lower()], + ["docker", "inspect", "-f", "{{.Id}}", expected], capture_output=True, text=True, ) if result.returncode == 0 and result.stdout.strip(): return result.stdout.strip() + + # Fallback: list all containers and do case-insensitive exact match + ps_result = subprocess.run( + ["docker", "ps", "-a", "--format", "{{.Names}}\t{{.ID}}"], + capture_output=True, + text=True, + ) + if ps_result.returncode == 0: + for line in ps_result.stdout.strip().splitlines(): + parts = line.split("\t") + if len(parts) == 2: + name, cid = parts + if name.lower() == expected: + return cid return None