# 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"]