Files
2026-07-13 13:12:33 +08:00

96 lines
3.7 KiB
Docker

# syntax=docker/dockerfile:1.6
#
# S20 — OpenSquilla container image.
#
# Safety contract:
# * Inside the container the gateway binds to 0.0.0.0 because the Docker
# network namespace needs a wildcard bind for `-p host:container`
# publishing to work. The defense-in-depth lives at the HOST-SIDE `-p`
# binding: the documented default `docker run -p 127.0.0.1:18791:18791`
# keeps the gateway reachable only from the host's loopback.
# * Network exposure is opt-in via `-p 0.0.0.0:18791:18791` — see the
# "Network exposure" section in README.md for the warning.
# * The S19 boot WARNING (`gateway.bind.public`) fires on every container
# start because the in-container bind is a wildcard by design — that is
# the intended signal to operators running the image.
FROM python:3.13-slim-bookworm
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
PIP_NO_CACHE_DIR=1
# --- safety default ---------------------------------------------------------
# OPENSQUILLA_LISTEN=0.0.0.0 is required inside the container so the gateway can
# be reached via Docker port publishing. Do NOT flip this to 127.0.0.1 —
# that would make the container reachable only from itself. The safe
# default for HOST-side exposure lives at `docker run -p`, not here.
ENV OPENSQUILLA_LISTEN=0.0.0.0 \
OPENSQUILLA_GATEWAY_PORT=18791
WORKDIR /app
# Build tooling for optional C-extension deps (jieba FTS5 tokenizer, etc.).
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
curl \
&& rm -rf /var/lib/apt/lists/*
# Copy minimal build context — everything else is in .dockerignore.
COPY pyproject.toml README.md README.release.md ./
COPY src/ ./src/
COPY migrations/ ./migrations/
RUN python - <<'PY'
from pathlib import Path
root = Path("src/opensquilla/squilla_router/models")
required = [
root / "v4.2_phase3_inference" / "lgbm_main.bin",
root / "v4.2_phase3_inference" / "router.runtime.yaml",
root / "v4.2_phase3_inference" / "mlp" / "model.onnx",
root / "v4.2_phase3_inference" / "features" / "tfidf.pkl",
root / "v4.2_phase3_inference" / "bge_onnx" / "model.onnx",
]
pointer = "version https://git-lfs.github.com/spec/v1"
missing = [str(path) for path in required if not path.is_file()]
pointers = []
for path in required:
if not path.is_file():
continue
first_line = path.read_text(encoding="utf-8", errors="ignore").splitlines()[0]
if first_line.strip() == pointer:
pointers.append(str(path))
if missing or pointers:
raise SystemExit(
"Squilla router v4 model assets are unavailable in this build context. "
'Run `git lfs pull --include="src/opensquilla/squilla_router/models/**"` '
f"before docker build. Missing={missing} Pointers={pointers}"
)
PY
RUN pip install ".[recommended]"
# Persisted state root. The gateway writes config, state, logs, and the
# workspace under OPENSQUILLA_STATE_DIR — mounting a volume here (see
# compose.yaml) is what makes a container's setup survive a recreate.
ENV OPENSQUILLA_STATE_DIR=/var/lib/opensquilla
# Run as a non-root user — avoids shipping root creds into production.
# The state root is created and owned by that user before the USER drop,
# so a freshly initialized volume inherits writable, non-root ownership.
RUN useradd --create-home --uid 10001 --shell /usr/sbin/nologin opensquilla \
&& mkdir -p /var/lib/opensquilla \
&& chown -R opensquilla:opensquilla /app /var/lib/opensquilla
USER opensquilla
EXPOSE 18791
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl --fail --silent --show-error http://127.0.0.1:18791/healthz || exit 1
ENTRYPOINT ["opensquilla"]
CMD ["gateway", "run"]