FROM python:3.11-slim AS dependencies ENV PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ NEEDLE_TELEMETRY=0 \ DO_NOT_TRACK=1 \ NEEDLE_INFERENCE_CONCURRENCY=1 \ NEEDLE_MAX_QUEUE_SIZE=16 \ NEEDLE_BODY_CONCURRENCY=16 \ NEEDLE_CONTEXT_BUDGET=2048 \ NEEDLE_STATE_BYTES=4194304 # Official Python slim already includes CA roots; verify them rather than # adding a redundant network-dependent apt layer. Never disable TLS validation. RUN python -c "import ssl; assert ssl.create_default_context().cert_store_stats()['x509_ca'] > 0" WORKDIR /srv COPY requirements.txt /srv/requirements.txt # Optional cloud proxy CA bundle is mounted only for this build step. It is # never baked into the image and verification remains enabled. RUN --mount=type=secret,id=cloud_ca \ if [ -f /run/secrets/cloud_ca ]; then \ export PIP_CERT=/run/secrets/cloud_ca SSL_CERT_FILE=/run/secrets/cloud_ca; \ fi; \ pip install --no-cache-dir -r requirements.txt # Local CPU regression target, independent of model download availability. FROM dependencies AS tests COPY requirements-dev.txt /srv/requirements-dev.txt RUN --mount=type=secret,id=cloud_ca \ if [ -f /run/secrets/cloud_ca ]; then export PIP_CERT=/run/secrets/cloud_ca; fi; \ pip install --no-cache-dir -r requirements-dev.txt COPY gateway.py models.py registry.py laya.py context_adapter.py decision_policy.py request_limits.py intent_guard.py payload_transport.py native_schema.py headroom_evaluation.py /srv/ COPY tests /srv/tests COPY benchmarks/intent/development.json /srv/benchmarks/intent/development.json COPY benchmarks/coverage/development.json /srv/benchmarks/coverage/development.json COPY pytest.ini /srv/pytest.ini ENV NEEDLE3_LIB_PATH=/nonexistent/needle3.so HF_HUB_OFFLINE=1 CMD ["python", "-m", "pytest", "-q", "tests"] FROM dependencies AS runtime # Bake the Needle 3 engine library and base needle3.cact weights into the # image at Needle's own default cache-directory convention # (~/.cache/cactus-needle/v3//), so `needle.Needle(...)` # resolves both locally with no network call at container start/restart. # # We deliberately do NOT pass an explicit weights= path to Needle() at # runtime (see gateway.py): cactus-needle==3.0.1 treats any explicit # `weights=` as a tuned checkpoint and permanently disables the calibrated # confidence head, which this service's routing policy depends on. Baking # into the default cache dir keeps both "no network at runtime" and a # calibrated confidence score. The Dockerfile stays root/HOME=/root for # the whole build and the running container so this cache path is the # same one Needle looks up at request time. RUN --mount=type=secret,id=cloud_ca \ if [ -f /run/secrets/cloud_ca ]; then \ export SSL_CERT_FILE=/run/secrets/cloud_ca REQUESTS_CA_BUNDLE=/run/secrets/cloud_ca; \ fi; \ NEEDLE_CACHE_DIR="$(python -c 'from needle.agent import fetch; print(fetch.cache_dir(3))')" \ && mkdir -p "$NEEDLE_CACHE_DIR" \ && needle fetch --generation 3 \ && needle download needle3 --generation 3 --out "$NEEDLE_CACHE_DIR" # HF_HUB_OFFLINE=1 makes a missing/misconfigured cache fail fast at # startup instead of silently reaching the network. ENV HF_HUB_OFFLINE=1 # Keep the exact official base assets used for the baseline comparison. An # upstream asset change fails the build rather than silently changing Needle. COPY needle-assets.sha256 /srv/needle-assets.sha256 RUN NEEDLE_CACHE_DIR="$(python -c 'from needle.agent import fetch; print(fetch.cache_dir(3))')" \ && cd "$NEEDLE_CACHE_DIR" \ && sha256sum --check /srv/needle-assets.sha256 COPY gateway.py models.py registry.py laya.py context_adapter.py decision_policy.py request_limits.py intent_guard.py payload_transport.py native_schema.py start.sh /srv/ RUN chmod +x /srv/start.sh EXPOSE 7860 ENTRYPOINT ["/srv/start.sh"]