From a5aab8e92af25ba912a74206d0c60bf068f3df72 Mon Sep 17 00:00:00 2001 From: phact Date: Wed, 8 Oct 2025 13:56:52 -0400 Subject: [PATCH] podman fixes: bind docling-serve to 0.0, improve logging, support magic podman and docker hostnames --- .env.example | 3 +++ src/api/docling.py | 32 +++++++++++++++++++++-------- src/tui/managers/docling_manager.py | 23 +++++++++++++-------- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/.env.example b/.env.example index 8d412670..f790ce09 100644 --- a/.env.example +++ b/.env.example @@ -37,6 +37,9 @@ AWS_SECRET_ACCESS_KEY= # OPTIONAL url for openrag link to langflow in the UI LANGFLOW_PUBLIC_URL= +# OPTIONAL: Override host for docling service (for special networking setups) +# HOST_DOCKER_INTERNAL=host.containers.internal + # Langflow auth LANGFLOW_AUTO_LOGIN=False LANGFLOW_SUPERUSER= diff --git a/src/api/docling.py b/src/api/docling.py index 66b7777e..22b709ef 100644 --- a/src/api/docling.py +++ b/src/api/docling.py @@ -41,23 +41,36 @@ def determine_docling_host() -> str: """Determine the host address used for docling health checks.""" container_type = detect_container_environment() if container_type: + # Try HOST_DOCKER_INTERNAL env var first container_host = get_container_host() if container_host: logger.info("Using container-aware host '%s'", container_host) return container_host + # Try special hostnames (Docker Desktop and rootless podman) + import socket + for hostname in ["host.docker.internal", "host.containers.internal"]: + try: + socket.getaddrinfo(hostname, None) + logger.info("Using %s for container-to-host communication", hostname) + return hostname + except socket.gaierror: + logger.debug("%s not available", hostname) + + # Try gateway IP detection (Docker on Linux) gateway_ip = _get_gateway_ip_from_route() if gateway_ip: logger.info("Detected host gateway IP: %s", gateway_ip) return gateway_ip - # Either we're not inside a container or gateway detection failed. - fallback_ip = guess_host_ip_for_containers(logger=logger) - if container_type: + # Fallback to bridge IP + fallback_ip = guess_host_ip_for_containers(logger=logger) logger.info("Falling back to container bridge host %s", fallback_ip) - else: - logger.info("Running outside a container; using host %s", fallback_ip) - return fallback_ip + return fallback_ip + + # Running outside a container + logger.info("Running outside a container; using localhost") + return "localhost" # Detect the host IP once at startup @@ -70,10 +83,11 @@ async def health(request: Request) -> JSONResponse: Proxy health check to docling-serve. This allows the frontend to check docling status via same-origin request. """ + health_url = f"{DOCLING_SERVICE_URL}/health" try: async with httpx.AsyncClient() as client: response = await client.get( - f"{DOCLING_SERVICE_URL}/health", + health_url, timeout=2.0 ) @@ -83,6 +97,7 @@ async def health(request: Request) -> JSONResponse: "host": HOST_IP }) else: + logger.warning("Docling health check failed", url=health_url, status_code=response.status_code) return JSONResponse({ "status": "unhealthy", "message": f"Health check failed with status: {response.status_code}", @@ -90,13 +105,14 @@ async def health(request: Request) -> JSONResponse: }, status_code=503) except httpx.TimeoutException: + logger.warning("Docling health check timeout", url=health_url) return JSONResponse({ "status": "unhealthy", "message": "Connection timeout", "host": HOST_IP }, status_code=503) except Exception as e: - logger.error("Docling health check failed", error=str(e)) + logger.error("Docling health check failed", url=health_url, error=str(e)) return JSONResponse({ "status": "unhealthy", "message": str(e), diff --git a/src/tui/managers/docling_manager.py b/src/tui/managers/docling_manager.py index 7cb5d1e8..e58a5b1e 100644 --- a/src/tui/managers/docling_manager.py +++ b/src/tui/managers/docling_manager.py @@ -8,7 +8,6 @@ import threading import time from typing import Optional, Tuple, Dict, Any, List, AsyncIterator from utils.logging_config import get_logger -from utils.container_utils import guess_host_ip_for_containers logger = get_logger(__name__) @@ -32,7 +31,8 @@ class DoclingManager: self._process: Optional[subprocess.Popen] = None self._port = 5001 - self._host = guess_host_ip_for_containers(logger=logger) # Get appropriate host IP based on runtime + # Bind to all interfaces by default (can be overridden with DOCLING_BIND_HOST env var) + self._host = os.getenv('DOCLING_BIND_HOST', '0.0.0.0') self._running = False self._external_process = False @@ -150,16 +150,20 @@ class DoclingManager: else: pid = self._load_pid() + # Use localhost for display URLs when bound to 0.0.0.0 + display_host = "localhost" if self._host == "0.0.0.0" else self._host + return { "status": "running", "port": self._port, "host": self._host, - "endpoint": f"http://{self._host}:{self._port}", - "docs_url": f"http://{self._host}:{self._port}/docs", - "ui_url": f"http://{self._host}:{self._port}/ui", + "endpoint": f"http://{display_host}:{self._port}", + "docs_url": f"http://{display_host}:{self._port}/docs", + "ui_url": f"http://{display_host}:{self._port}/ui", "pid": pid } else: + display_host = "localhost" if self._host == "0.0.0.0" else self._host return { "status": "stopped", "port": self._port, @@ -176,10 +180,9 @@ class DoclingManager: return False, "Docling serve is already running" self._port = port - # Use provided host or the bridge IP we detected in __init__ + # Use provided host or keep default from __init__ if host is not None: self._host = host - # else: keep self._host as already set in __init__ # Check if port is already in use before trying to start import socket @@ -293,7 +296,8 @@ class DoclingManager: self._running = False return False, f"Docling serve process exited immediately (code: {return_code})" - return True, f"Docling serve starting on http://{host}:{port}" + display_host = "localhost" if self._host == "0.0.0.0" else self._host + return True, f"Docling serve starting on http://{display_host}:{port}" except FileNotFoundError: return False, "docling-serve not available. Please install: uv add docling-serve" @@ -454,7 +458,8 @@ class DoclingManager: async def follow_logs(self) -> AsyncIterator[str]: """Follow logs from the docling-serve process in real-time.""" # First yield status message and any existing logs - status_msg = f"Docling serve is running on http://{self._host}:{self._port}" + display_host = "localhost" if self._host == "0.0.0.0" else self._host + status_msg = f"Docling serve is running on http://{display_host}:{self._port}" with self._log_lock: if self._log_buffer: