podman fixes: bind docling-serve to 0.0, improve logging, support magic podman and docker hostnames

This commit is contained in:
phact 2025-10-08 13:56:52 -04:00
parent 0dcfff15b3
commit a5aab8e92a
3 changed files with 41 additions and 17 deletions

View file

@ -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=

View file

@ -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),

View file

@ -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: