|
1 | | -# ─── Stage 1: install dependencies ─────────────────────────────────────────── |
| 1 | +# ─── Stage 1: heavy stable dependencies (variant-aware) ────────────────────── |
| 2 | +# Two image variants are published from this Dockerfile: |
| 3 | +# - slim (default, `:latest`) — ~450 MB. cocoindex-code + LiteLLM only. |
| 4 | +# For users who'll point the embedding at a cloud provider (OpenAI, |
| 5 | +# Voyage, Gemini, …). |
| 6 | +# - full (`:full`) — ~5 GB. Also bundles sentence-transformers |
| 7 | +# + torch + a pre-baked default model. For users who want offline-ready |
| 8 | +# local embeddings without an API key. |
| 9 | +# |
| 10 | +# This stage installs only the big, slow-changing deps that are shared across |
| 11 | +# releases: |
| 12 | +# - full: `sentence-transformers` (pulls torch + transformers + tokenizers |
| 13 | +# transitively, ~1 GB of wheels). |
| 14 | +# - slim: nothing — cocoindex-code's LiteLLM deps get installed in stage 2. |
| 15 | +# |
| 16 | +# The cache key is the RUN command string, which changes with CCC_VARIANT, so |
| 17 | +# BuildKit keeps separate cache entries per variant and reuses each across |
| 18 | +# releases until we bump the deps. |
| 19 | +# |
| 20 | +# `cocoindex` and `cocoindex-code` are deliberately NOT installed here — |
| 21 | +# they bump often, so pinning them at this layer would invalidate the heavy |
| 22 | +# cache on every release. Stage 2 installs them on top; transitive deps are |
| 23 | +# already satisfied, so uv only fetches the two packages themselves. |
| 24 | +# |
2 | 25 | # Use slim (glibc-based) — cocoindex ships pre-built Rust wheels that need glibc. |
3 | 26 | # Alpine / musl-libc would require building from source. |
4 | | -FROM python:3.12-slim AS builder |
| 27 | +# |
| 28 | +# `--system` tells uv to install into the base Python at |
| 29 | +# /usr/local/lib/python3.12/... since there's no virtualenv in the image. |
| 30 | +FROM python:3.12-slim AS deps |
5 | 31 |
|
6 | 32 | RUN pip install --quiet uv |
7 | 33 |
|
| 34 | +ARG CCC_VARIANT=slim |
| 35 | +RUN if [ "$CCC_VARIANT" = "full" ]; then \ |
| 36 | + uv pip install --system --prerelease=allow sentence-transformers; \ |
| 37 | + fi |
| 38 | + |
| 39 | +# ─── Stage 2: install cocoindex + cocoindex-code (per release) ─────────────── |
| 40 | +# Cheap relative to stage 1: transitive deps like torch are already in place |
| 41 | +# for the full variant; for slim there are no heavy deps to pull. uv only |
| 42 | +# needs to fetch the cocoindex + cocoindex-code wheels themselves. |
| 43 | +FROM deps AS builder |
8 | 44 | WORKDIR /build |
| 45 | +ARG CCC_VARIANT=slim |
9 | 46 |
|
10 | | -# Default: install the released cocoindex-code from PyPI (release flow). |
11 | | -# Tests/local dev override with: |
12 | | -# --build-arg CCC_INSTALL_SPEC=/ccc-src[default] |
13 | | -# which installs from the copied-in source tree instead. The COPY always runs; |
14 | | -# with .dockerignore trimming build artifacts it adds ~nothing. |
15 | | -ARG CCC_INSTALL_SPEC="cocoindex-code[default]" |
| 47 | +# Default behaviour: install cocoindex-code from PyPI, picking the extras |
| 48 | +# that match CCC_VARIANT. |
| 49 | +# Release workflow / local tests override with (respectively): |
| 50 | +# --build-arg CCC_INSTALL_SPEC=/ccc-src |
| 51 | +# --build-arg CCC_INSTALL_SPEC=/ccc-src[full] |
| 52 | +ARG CCC_INSTALL_SPEC="" |
16 | 53 | COPY . /ccc-src |
| 54 | +RUN if [ -z "$CCC_INSTALL_SPEC" ]; then \ |
| 55 | + if [ "$CCC_VARIANT" = "full" ]; then \ |
| 56 | + CCC_INSTALL_SPEC="cocoindex-code[full]"; \ |
| 57 | + else \ |
| 58 | + CCC_INSTALL_SPEC="cocoindex-code"; \ |
| 59 | + fi; \ |
| 60 | + fi; \ |
| 61 | + uv pip install --system --prerelease=allow \ |
| 62 | + "cocoindex>=1.0.0a33" \ |
| 63 | + "${CCC_INSTALL_SPEC}" |
17 | 64 |
|
18 | | -RUN uv pip install --system --prerelease=allow \ |
19 | | - "cocoindex>=1.0.0a33" \ |
20 | | - "${CCC_INSTALL_SPEC}" |
21 | | - |
22 | | -# ─── Stage 2: pre-bake the default embedding model ──────────────────────────── |
23 | | -# Bakes Snowflake/snowflake-arctic-embed-xs into the merged data directory at |
24 | | -# /var/cocoindex/cache/..., so on first run Docker's volume copy-up populates |
25 | | -# the cocoindex-data volume with the model — no network fetch needed. |
| 65 | +# ─── Stage 3: pre-bake the default embedding model (full only) ─────────────── |
| 66 | +# For the full variant, bakes Snowflake/snowflake-arctic-embed-xs into |
| 67 | +# /var/cocoindex/cache/... so Docker's first-mount copy-up populates the |
| 68 | +# cocoindex-data volume with the model — no network fetch on first start. |
| 69 | +# For slim, just creates empty cache dirs so the runtime stage's COPY works |
| 70 | +# regardless of variant. |
26 | 71 | FROM builder AS model_cache |
| 72 | +ARG CCC_VARIANT=slim |
27 | 73 |
|
28 | 74 | ENV HF_HOME=/var/cocoindex/cache/huggingface \ |
29 | 75 | SENTENCE_TRANSFORMERS_HOME=/var/cocoindex/cache/sentence-transformers |
30 | 76 |
|
31 | 77 | RUN mkdir -p /var/cocoindex/cache/huggingface /var/cocoindex/cache/sentence-transformers \ |
32 | | - && python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('Snowflake/snowflake-arctic-embed-xs'); print('Model cached.')" |
| 78 | + && if [ "$CCC_VARIANT" = "full" ]; then \ |
| 79 | + python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('Snowflake/snowflake-arctic-embed-xs'); print('Model cached.')"; \ |
| 80 | + fi |
33 | 81 |
|
34 | | -# ─── Stage 3: runtime ───────────────────────────────────────────────────────── |
| 82 | +# ─── Stage 4: runtime ───────────────────────────────────────────────────────── |
35 | 83 | FROM python:3.12-slim AS runtime |
36 | 84 |
|
37 | 85 | # gosu for privilege-drop (PUID/PGID pattern); create non-root coco user. |
|
0 commit comments