fix: case-insensitive container name matching for docker inspect

Docker container names are case-sensitive for 'docker inspect' but case-
insensitive for Docker DNS. Compose templates may render container names
with mixed case (e.g. code-server-Headquarter-abc123), causing exact-name
docker inspect to fail while DNS resolution in tunnels works fine.

- apps/api/src/services/docker.py: get_container_id now tries exact match
  first, then falls back to case-insensitive exact match via 'docker ps'
- apps/api/src/api/tool_instances.py: recreate_tunnel_endpoint uses
  get_container_id instead of its own docker inspect call

Quality gates: ruff clean
This commit is contained in:
2026-05-30 15:19:42 +02:00
parent 2c2c4f3683
commit 351e76c00d
2 changed files with 28 additions and 19 deletions
+5 -15
View File
@@ -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",
+23 -4
View File
@@ -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