diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..054d72d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,20 @@ +.git +.github +.venv +__pycache__ +*.pyc +.env +.env.example +runs/ +screenshots/ +integrations/ +PYTHIA.app/ +PYTHIA.command +run-all.sh +*.md +LICENSE +logo.png +demo.mp4 +.gitignore +.dockerignore +docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9ff4440 --- /dev/null +++ b/Dockerfile @@ -0,0 +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 uv sync --frozen --no-dev + +COPY engine/ ./engine/ + +# ── 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" \ + 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 new file mode 100644 index 0000000..be58515 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,53 @@ +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" + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/health"] + interval: 15s + timeout: 5s + start_period: 30s + retries: 5 + 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 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} + - 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: + condition: service_healthy + extra_hosts: + - "host.docker.internal:host-gateway" + restart: unless-stopped diff --git a/engine/server.py b/engine/server.py index ad5c04f..51bd602 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", "version": app.version} + + @app.get("/config") async def config(): return CONFIG.summary()