From 5e373ea20cb50c0067dc7c4cb852014156fa3ac9 Mon Sep 17 00:00:00 2001 From: Suriya Date: Sun, 16 Aug 2026 09:52:52 +0530 Subject: [PATCH] Trim ~350MB of unreachable payload from the backend image The image was 2.45GB, of which the venv is 1.73GB. The build was already multi-stage and already installed CPU-only torch, so the remaining weight was not build tooling - it was payload inside the installed packages that the running service can never execute. Removed in the build stage, before the runtime stage copies /opt/venv, so the bytes never enter the final image: - bundled test suites (~237MB; torch/test is 83MB, pandas/tests 40MB) - torch/include (62MB), C++ headers for compiling against libtorch - torch/bin (50MB), gtest binaries and protoc; torch_shm_manager is kept pytest and httpx2 move to requirements-dev.txt: the image copies app/, cli/, scripts/, data/ and serve.py, never tests/, so the test stack was unusable there regardless. sympy was checked and deliberately kept - 'import sentence_transformers' does pull it in through torch.fx, so removing it would break embeddings. Co-Authored-By: Claude Opus 5 --- Dockerfile | 26 ++++++++++++++++++++++++++ README.md | 5 +++-- requirements-dev.txt | 14 ++++++++++++++ requirements.txt | 9 +++------ 4 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 requirements-dev.txt diff --git a/Dockerfile b/Dockerfile index 12b0e62..dbacaa1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,6 +37,32 @@ RUN python -m venv /opt/venv \ --index-url https://download.pytorch.org/whl/cpu torch \ && /opt/venv/bin/pip install --no-cache-dir -r requirements.txt +# Strip payload the running service can never execute. Doing this in the build +# stage is what makes it count: the runtime stage copies /opt/venv as one layer, +# so anything deleted after that COPY would still occupy space in the layer +# below it. Deleting it here means the bytes are never in the final image. +# +# Measured on this image, the venv was 1.65GB, and this removes ~350MB of it: +# +# - Bundled test suites (~237MB, of which torch/test alone is 83MB). Every +# scientific wheel ships its own; pandas/tests is 40MB. Matched on exactly +# `tests`/`test` so numpy.testing and sklearn.utils._testing - which ARE +# imported by library code at runtime - are left alone. +# - torch/include (62MB): C++ headers, needed only to COMPILE an extension +# against libtorch. Nothing here does; torch is used through Python. +# - torch/bin (50MB): C++ gtest binaries (test_api is 15MB, test_jit 13.5MB) +# plus a protoc. torch_shm_manager is the one real program in there - it +# brokers shared-memory tensors between processes - so it is kept. +# +# Verified against this app rather than assumed: sympy IS pulled in by `import +# sentence_transformers` (via torch.fx), so it stays despite being 80MB and +# looking like a pure-math dependency nothing here would want. +RUN set -eux; \ + SP=/opt/venv/lib/python3.11/site-packages; \ + find "$SP" -type d \( -name tests -o -name test \) -prune -exec rm -rf {} +; \ + rm -rf "$SP/torch/include"; \ + find "$SP/torch/bin" -type f ! -name torch_shm_manager -delete + # ---- Runtime stage ---- FROM python:3.11-slim AS runtime diff --git a/README.md b/README.md index 393c018..f676610 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ It does **not** start Postgres or Ollama for you - it reports them via cd backend python3 -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate -pip install -r requirements.txt +pip install -r requirements.txt -r requirements-dev.txt # -dev is pytest only cp .env.example .env # then edit DB_PASSWORD etc. @@ -198,7 +198,8 @@ backend/ ├── cli/ingest_brand.py # CLI: ingest one brand end-to-end ├── scripts/seed_sample_data.py # load bundled sample catalogs (no LLM needed) ├── data/seed_catalogs/*.json # bundled sample catalogs (Parle, Cadbury, ...) -└── requirements.txt +├── requirements.txt +└── requirements-dev.txt ``` ## Running tests diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..33f06a2 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,14 @@ +# Test-only dependencies, kept out of requirements.txt so the Docker image does +# not ship them - the container has no tests/ directory to run anyway (the +# Dockerfile copies app/, cli/, scripts/, data/ and serve.py, nothing else). +# +# For development, install both files: +# pip install -r requirements.txt -r requirements-dev.txt + +pytest>=8.3.3 + +# Starlette's TestClient deprecates the httpx 0.x backend and emits a +# StarletteDeprecationWarning without this. pytest.ini turns warnings into +# errors, so it is a hard requirement of the suite, not a nicety. Test-only: +# the application itself uses the `httpx` pinned in requirements.txt. +httpx2>=2.10.0 diff --git a/requirements.txt b/requirements.txt index 6783c53..6801e76 100644 --- a/requirements.txt +++ b/requirements.txt @@ -76,9 +76,6 @@ scipy>=1.13.1 joblib>=1.4.2 # --- Dev/test tooling --- -pytest>=8.3.3 -# Starlette's TestClient deprecates the httpx 0.x backend and emits a -# StarletteDeprecationWarning without this. pytest.ini turns warnings into -# errors, so it is a hard requirement of the suite, not a nicety. Test-only: -# the application itself uses the `httpx` pinned above. -httpx2>=2.10.0 +# Moved to requirements-dev.txt so the Docker image does not carry the test +# stack it can never run. To work on this project, install both: +# pip install -r requirements.txt -r requirements-dev.txt