# Multi-stage image for the sengol-api server used by docker-compose.
# Stays Apache-2.0 — no proprietary base or wheels.
#
# Base image matches CI's interpreter (Python 3.13) so what we test is what we
# ship. Bump CI in lockstep if you change this.
#
# Stage 1 (console) builds the Console SPA with Node so the runtime image
# ships it inside the installed package (sengol/console_static/ — see
# console/vite.config.ts outDir). Without this stage the Console is
# unreachable in the shipped image, even though the code to serve it exists.

FROM node:22-slim AS console

WORKDIR /app/console

# Install deps first so the layer cache holds across source-only changes.
COPY console/package.json console/package-lock.json ./
RUN npm ci

COPY console/ ./
# Output lands at /app/sengol/console_static per vite.config.ts's
# `build.outDir: "../sengol/console_static"` (relative to console/).
RUN npm run build


FROM python:3.13-slim AS runtime

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1 \
    UV_SYSTEM_PYTHON=1

WORKDIR /app

# The base image's Debian packages lag their security updates, so the runtime
# layer takes the distro's current ones. ci.yml's Grype gate fails the build on
# a high/critical CVE in an OS package whose fix a release can TAKE — one whose
# every published fix is a pre-release is reported and does not block there, or
# on a deploy, though it still stops a publish. These are where they live:
# libc6, perl-base, gzip, libpcre2.
RUN apt-get update \
 && apt-get upgrade -y \
 && rm -rf /var/lib/apt/lists/*

# The base image's pip, setuptools and wheel lag their fixed releases and
# would ship in the runtime layer, so they move to current before anything
# else; uv installs the application below. This layer is cached across SDK
# code changes.
RUN pip install --no-cache-dir --upgrade pip setuptools wheel uv

# Copy metadata + source. uv reads README.md when resolving the package, so
# the .dockerignore must keep it in the build context.
COPY pyproject.toml uv.lock README.md alembic.ini ./
COPY alembic ./alembic
COPY sengol ./sengol

# The published demo: its compose file and seeder, so the public quickstart
# (docs.sengol.io/quickstart) needs no checkout. Read-only files outside the
# installed package.
COPY deploy/demo/docker-compose.yml ./demo/docker-compose.yml
COPY scripts/seed_demo.py ./scripts/seed_demo.py

# Built Console assets — must land here BEFORE `uv pip install --system .` so
# they're picked up by the package build (console_static/ is gitignored;
# pyproject.toml's [tool.hatch.build.targets.wheel] artifacts entry
# force-includes it despite that).
COPY --from=console /app/sengol/console_static ./sengol/console_static

# Install dependencies + the package itself into the SYSTEM interpreter so
# the `uvicorn` entrypoint below resolves the right environment without
# requiring `uv run` at runtime.
RUN uv pip install --system --no-cache ".[otlp,server,reports,model,s3,openai-judge]" \
    && uv pip install --system --no-cache "uvicorn[standard]"

# Installer tooling has no role at runtime; removing it shrinks the image and
# what the vulnerability scan has to clear. setuptools stays because torch
# (the `model` extra) imports it. The import line proves the environment is
# still whole after the removal.
RUN pip uninstall -y uv wheel && python -m pip uninstall -y pip \
    && python -c "import sengol, uvicorn, alembic, sengol.api.app"

EXPOSE 8080

# Drop privileges. The image runs as a non-root, non-login user.
RUN useradd --uid 10001 --user-group --no-create-home --shell /usr/sbin/nologin sengol
USER sengol

HEALTHCHECK --interval=15s --timeout=3s --start-period=10s --retries=3 \
    CMD python -c "import urllib.request, sys; \
sys.exit(0 if urllib.request.urlopen('http://localhost:8080/health', timeout=2).status == 200 else 1)"

CMD ["uvicorn", "sengol.api.app:app", "--host", "0.0.0.0", "--port", "8080"]
