From c4389e66b14ec0e0e230c6506e0dd0eb9023035d Mon Sep 17 00:00:00 2001 From: 3L0935 Date: Sat, 4 Jul 2026 19:01:44 +0200 Subject: [PATCH 1/2] feat: Docker engine + unified compose + root health route MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three changes to make Pythia self-hostable without fragile frontend patches: 1. **Dockerfile for the engine** — multi-stage Python 3.13 Alpine build (~80MB final image). Uses uv for fast dependency resolution. No Osiris overlay needed — the engine talks to Osiris via HTTP. 2. **docker-compose.yml** — unified stack: Osiris (GHCR prebuilt) + Pythia engine (local build). Docker internal network so Pythia reaches Osiris at http://osiris:3000. All config via env vars with sensible defaults. Single 'docker compose up -d' to start. 3. **Root health route (GET /)** — FastAPI returns 404 on / by default, which breaks load balancers, service monitors (Docker healthcheck, Hermes Hub, k8s probes). Added a lightweight root route returning {"status":"ok","service":"pythia-oracle"}. Also added .dockerignore to keep the image lean. Why: the current install requires manually patching 15+ files into an Osiris checkout. This compose file gives you the full agent API (/agent/view, /agent/events, /predictions, /chat, /state/stream) with zero frontend patches — just docker compose up. --- .dockerignore | 15 +++++++++++++++ Dockerfile | 14 ++++++++++++++ docker-compose.yml | 40 ++++++++++++++++++++++++++++++++++++++++ engine/server.py | 6 ++++++ 4 files changed, 75 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..977e241 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +.git +.venv +__pycache__ +*.pyc +.env +runs/ +screenshots/ +integrations/ +PYTHIA.app/ +PYTHIA.command +run-all.sh +README.md +CONTRIBUTING.md +LICENSE +logo.png diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f3bac98 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.13-alpine AS builder +WORKDIR /app +COPY pyproject.toml uv.lock ./ +RUN pip install uv && uv sync --frozen --no-dev +COPY engine/ ./engine/ + +FROM python:3.13-alpine +WORKDIR /app +COPY --from=builder /app/.venv /app/.venv +COPY --from=builder /app/engine /app/engine +COPY --from=builder /app/pyproject.toml /app/ +ENV PATH="/app/.venv/bin:$PATH" +EXPOSE 8088 +CMD ["python", "-m", "engine.run"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..21c2422 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,40 @@ +name: pythia +services: + osiris: + image: ghcr.io/aiacos/osiris:latest + container_name: osiris + ports: + - "3000:3000" + environment: + - NODE_OPTIONS=--dns-result-order=ipv4first + - NODE_ENV=production + - PORT=3000 + - HOSTNAME=0.0.0.0 + extra_hosts: + - "host.docker.internal:host-gateway" + restart: unless-stopped + + pythia: + build: + context: . + dockerfile: Dockerfile + container_name: pythia-engine + ports: + - "8088:8088" + environment: + - OSIRIS_URL=http://osiris:3000 + - ENGINE_HOST=0.0.0.0 + - ENGINE_PORT=8088 + - LLM_BASE_URL=${LLM_BASE_URL:-http://localhost:11434/v1} + - LLM_API_KEY=${LLM_API_KEY:-ollama} + - LLM_MODEL=${LLM_MODEL:-llama3.1} + - ORACLE_TEMPERATURE=${ORACLE_TEMPERATURE:-0.5} + - ORACLE_TIMEOUT_SEC=${ORACLE_TIMEOUT_SEC:-180} + - HORIZONS=${HORIZONS:-24h,week,month,year} + - PREDICTIONS_PER_HORIZON=${PREDICTIONS_PER_HORIZON:-3} + - LOOP_INTERVAL_SEC=${LOOP_INTERVAL_SEC:-900} + - SENSE_INTERVAL_SEC=${SENSE_INTERVAL_SEC:-180} + - SWARM_ENABLED=${SWARM_ENABLED:-true} + depends_on: + - osiris + restart: unless-stopped diff --git a/engine/server.py b/engine/server.py index ad5c04f..e385ba9 100644 --- a/engine/server.py +++ b/engine/server.py @@ -49,6 +49,12 @@ async def health(): return {"status": "ok", "service": "pythia-oracle", "config": CONFIG.summary()} +@app.get("/") +async def root(): + """Root health-check route for load balancers and service monitors.""" + return {"status": "ok", "service": "pythia-oracle"} + + @app.get("/config") async def config(): return CONFIG.summary() From 36ba2527d139aef5b73914dc9054a27c808e52ef Mon Sep 17 00:00:00 2001 From: 3L0935 Date: Sat, 4 Jul 2026 19:39:30 +0200 Subject: [PATCH 2/2] improve: Dockerfile healthcheck & layer cache, compose healthcheck + condition, .dockerignore coverage Follow-up improvements to the Docker engine PR: Dockerfile: - Split pip install uv from uv sync for better layer caching - Add --no-cache-dir to pip install to keep builder lean - Add HEALTHCHECK using the root route (GET /) - Add PYTHONUNBUFFERED=1 and PYTHIA_ROOT env vars - Named runtime stage for clarity docker-compose.yml: - Add healthcheck on osiris service (wget /api/health) - depends_on now uses condition: service_healthy - Fix LLM_BASE_URL default: localhost is unreachable from a container; use host.docker.internal instead (with extra_hosts mapping) - Add extra_hosts to pythia service for host.docker.internal on Linux .dockerignore: - Add .github, .env.example, demo.mp4, .gitignore, .dockerignore, docker-compose.yml - Use *.md glob instead of enumerating README.md + CONTRIBUTING.md engine/server.py: - Root route now includes app.version for debugging --- .dockerignore | 9 +++++++-- Dockerfile | 25 ++++++++++++++++++++++--- docker-compose.yml | 17 +++++++++++++++-- engine/server.py | 2 +- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/.dockerignore b/.dockerignore index 977e241..054d72d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,15 +1,20 @@ .git +.github .venv __pycache__ *.pyc .env +.env.example runs/ screenshots/ integrations/ PYTHIA.app/ PYTHIA.command run-all.sh -README.md -CONTRIBUTING.md +*.md LICENSE logo.png +demo.mp4 +.gitignore +.dockerignore +docker-compose.yml diff --git a/Dockerfile b/Dockerfile index f3bac98..9ff4440 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,33 @@ FROM python:3.13-alpine AS builder WORKDIR /app + +# Install uv without pulling pip's cache into the image +RUN pip install --no-cache-dir uv + +# Resolve & install dependencies first — this layer is cached unless +# pyproject.toml or uv.lock change, so engine-only edits don't reinstall deps. COPY pyproject.toml uv.lock ./ -RUN pip install uv && uv sync --frozen --no-dev +RUN uv sync --frozen --no-dev + COPY engine/ ./engine/ -FROM python:3.13-alpine +# ── runtime stage ── +FROM python:3.13-alpine AS runtime WORKDIR /app + +# The virtualenv has everything; just copy it plus the engine source. COPY --from=builder /app/.venv /app/.venv COPY --from=builder /app/engine /app/engine COPY --from=builder /app/pyproject.toml /app/ -ENV PATH="/app/.venv/bin:$PATH" + +ENV PATH="/app/.venv/bin:$PATH" \ + PYTHIA_ROOT="/app" \ + PYTHONUNBUFFERED=1 + EXPOSE 8088 + +# Lightweight root-route healthcheck — matches the GET / route added in server.py +HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8088/').read()" || exit 1 + CMD ["python", "-m", "engine.run"] diff --git a/docker-compose.yml b/docker-compose.yml index 21c2422..be58515 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,5 @@ name: pythia + services: osiris: image: ghcr.io/aiacos/osiris:latest @@ -12,6 +13,12 @@ services: - HOSTNAME=0.0.0.0 extra_hosts: - "host.docker.internal:host-gateway" + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/health"] + interval: 15s + timeout: 5s + start_period: 30s + retries: 5 restart: unless-stopped pythia: @@ -25,7 +32,10 @@ services: - OSIRIS_URL=http://osiris:3000 - ENGINE_HOST=0.0.0.0 - ENGINE_PORT=8088 - - LLM_BASE_URL=${LLM_BASE_URL:-http://localhost:11434/v1} + # LLM defaults point at a host-side Ollama; override via .env or shell env. + # host.docker.internal works on Docker Desktop; on Linux add --add-host or use + # the host network's IP (e.g. 172.17.0.1 for default bridge). + - LLM_BASE_URL=${LLM_BASE_URL:-http://host.docker.internal:11434/v1} - LLM_API_KEY=${LLM_API_KEY:-ollama} - LLM_MODEL=${LLM_MODEL:-llama3.1} - ORACLE_TEMPERATURE=${ORACLE_TEMPERATURE:-0.5} @@ -36,5 +46,8 @@ services: - SENSE_INTERVAL_SEC=${SENSE_INTERVAL_SEC:-180} - SWARM_ENABLED=${SWARM_ENABLED:-true} depends_on: - - osiris + osiris: + condition: service_healthy + extra_hosts: + - "host.docker.internal:host-gateway" restart: unless-stopped diff --git a/engine/server.py b/engine/server.py index e385ba9..51bd602 100644 --- a/engine/server.py +++ b/engine/server.py @@ -52,7 +52,7 @@ async def health(): @app.get("/") async def root(): """Root health-check route for load balancers and service monitors.""" - return {"status": "ok", "service": "pythia-oracle"} + return {"status": "ok", "service": "pythia-oracle", "version": app.version} @app.get("/config")