Air-gapped deployment toolkit for Jina AI models
15
stars
154
commits
Python
primary language
Aug 13, 2026
updated
On-prem deployment toolkit for Jina AI models. Bundle embedding, reranker, and reader models into self-contained Docker images that run fully offline.
New here? The Quick Start wiki page gets you to your first
/v1/embeddingsresponse in 5 minutes using a prebuilt image. Full tutorials, troubleshooting, and the model catalog live in the wiki.
![]()

flowchart TB
subgraph Phase1["Phase 1: BUNDLE - requires network"]
A["python jina-on-prem.py bundle"] --> B["Select model + runtime"]
B --> C["Docker build, weights + deps baked in"]
C --> D["docker save - .tar.gz"]
end
Phase1 -->|"USB / SCP / physical media"| Phase2
subgraph Phase2["Phase 2: DEPLOY - no network needed"]
E["docker load < model.tar.gz"] --> F["docker run -p 8080:8080"]
F --> G["Multi-schema API ready: OpenAI / Gemini / Cohere / Voyage"]
G --> H["Elasticsearch / Your App"]
end
./scripts/pull-prebuilt.sh jina-embeddings-v5-text-nano cpu
# produces jina-embeddings-v5-text-nano-cpu.tar.gz
# transfer it, then on the offline machine:
docker load < jina-embeddings-v5-text-nano-cpu.tar.gz
docker run -p 8080:8080 jina/jina-embeddings-v5-text-nano:cpu
On a Mac (Apple Silicon)? Prebuilt images are
linux/amd64only, so a plaindocker pullfails withno matching manifest for linux/arm64/v8.pull-prebuilt.shhandles this for you, falling back tolinux/amd64only when the registry has nothing for your architecture; if you pull by hand, add the flag to both commands:docker pull --platform linux/amd64 ...anddocker run --platform linux/amd64 .... It then runs under Rosetta emulation, correct but slow - fine for a first look, not for a benchmark. To develop against the API on a Mac, skip Docker and run the server directly:python jina-on-prem.py serveuses native arm64 PyTorch.
python jina-on-prem.py list # show all models
python jina-on-prem.py bundle # interactive picker
python jina-on-prem.py bundle --model jina-embeddings-v5-text-nano --cpu-only --yes
Need a builder machine? scripts/bootstrap-gcp.sh provisions one on GCP with Docker + NVIDIA Container Toolkit + the repo pre-cloned.
No repo, no scripts, no Python. Just Docker.
docker load < MODEL.tar.gz
docker run -p 8080:8080 jina/MODEL:cpu # CPU
docker run --gpus all -p 8080:8080 jina/MODEL:gpu # GPU
curl http://localhost:8080/health
Or via docker compose:
MODEL=jina-embeddings-v5-text-nano RUNTIME=cpu docker compose up -d
# for embed + rerank side-by-side:
docker compose -f docker-compose.multi.yml up -d
uv pip install openai requests
python examples/python_client.py
Drops in via OpenAI SDK with base_url="http://your-host:8080/v1".
29 models supported: embeddings (v5, v4, v3, v2), rerankers, readers, ColBERT, CLIP, VLM. 28 of them have prebuilt images; every model can be bundled from source. Headline picks:
| Model | Type | Modality | Params | VRAM | Prebuilt |
|---|---|---|---|---|---|
jina-embeddings-v5-text-nano | embedding | text | 239M | ~2GB | cpu / gpu |
jina-embeddings-v5-text-small | embedding | text | 677M | ~3GB | cpu / gpu |
jina-embeddings-v3 | embedding | text | 570M | ~3GB | cpu / gpu |
jina-embeddings-v5-omni-nano | embedding | multimodal | 1.04B | ~5GB | cpu / gpu |
jina-embeddings-v5-omni-small | embedding | multimodal | 1.74B | ~8GB | cpu / gpu |
jina-clip-v2 | embedding | multimodal | 865M | ~4GB | cpu / gpu |
jina-reranker-v3.5 | reranker | text | 0.6B | ~3GB | cpu / gpu |
jina-reranker-v3 | reranker | text | 597M | ~3GB | cpu / gpu |
Full catalog with all 29 models, VRAM, context windows, and licenses: Model Catalog wiki (auto-generated from models/catalog.json).
CC-BY-NC-4.0 models require a commercial license for production use. Contact Elastic sales.
:gpu-opt)The default :gpu server runs one forward pass at a time, so concurrent clients queue behind each other. The :gpu-opt tags add a server-side dynamic batcher: one GPU worker coalesces concurrent requests into length-sorted, token-budgeted batches, so clients can send one input at a time and still keep the GPU busy.
Published for the 16 embedding models. Rerankers, ColBERT, reader and VLM models have :cpu and :gpu only:
docker run --gpus all -p 8080:8080 \
ghcr.io/jina-ai/jina-on-prem/jina-embeddings-v3:gpu-opt
Same weights, same API, same output as :gpu — verified per model, not assumed: on eight of the sixteen the vectors are identical to the last bit, and on the rest they agree to cosine ≥ 0.9998. Still multi-task: every task (retrieval/text-matching/clustering/classification) behaves as it does on :gpu. fp16 by default, matching the stock :gpu dtype.
Where the batcher earns its keep, and where it does not:
input arrays — no gain. The client is already batching, and :gpu is the simpler choice.Whichever image you run, ask for encoding_format: "base64" — same numbers, and up to 2.6× the throughput on bulk requests, because a 768-dimension vector goes out as one string instead of 768 JSON floats. The OpenAI SDK does this by default. Tunable via JINA_BATCH_TOKENS, JINA_BATCH_WAIT_MS and JINA_DTYPE, with defaults baked in.
Which image for which traffic, the measured :gpu / :gpu-opt comparison, and per-model image sizes: Sizing & Hardware.
The server speaks five schemas on the same port simultaneously:
# Jina / OpenAI / Voyage AI
curl http://localhost:8080/v1/embeddings \
-H 'Content-Type: application/json' \
-d '{"input": ["Hello world"]}'
# Cohere
curl http://localhost:8080/v2/embed \
-H 'Content-Type: application/json' \
-d '{"texts": ["hi"], "input_type": "search_query"}'
# Google Gemini
curl 'http://localhost:8080/v1/models/MODEL:embedContent' \
-H 'Content-Type: application/json' \
-d '{"content": {"parts": [{"text": "hi"}]}, "taskType": "RETRIEVAL_QUERY"}'
# Reranker
curl http://localhost:8080/v1/rerank \
-H 'Content-Type: application/json' \
-d '{"query": "best embedding model", "documents": ["..."], "top_n": 2}'
# Reader / VLM models generate text instead of vectors
curl http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"messages": [{"role": "user", "content": "hi"}]}'
Tasks (retrieval, text-matching, classification, ...), matryoshka truncation (dimensions: 128), and multimodal inputs (omni / clip / v4 / vlm models): see the API Reference wiki.
Elasticsearch inference service drop-in: Elasticsearch integration.
Optional offline entitlement signal: a signed, expiring key that records a visible "expires on X" date against a deployment. Fully air-gapped (local HMAC check, no phone-home); issuing/renewing needs no image rebuild (key injected at run time).
python jina-on-prem.py keygen --sub acme-corp --days 90 # mint a 90-day key
docker run -e JINA_LICENSE_KEY=JINA-xxx.yyy -p 8080:8080 jina/MODEL:cpu
curl -s http://localhost:8080/health # shows license status; /health always open
Fail-open by default: a running deployment is never blocked. The default warn mode always serves - a missing/expired/invalid key only logs and shows in /health. Hard 403 blocking is opt-in via JINA_LICENSE_MODE=enforce (trials/POCs only), and even then an expired key survives a grace window. Compliance speed-bump, not DRM - the signing secret ships in the image, so this is an honest-system check rather than a lock. Details: Licensing wiki.
Two-phase model: bundle (Phase 1, connected) and deploy (Phase 2, offline). Same terminology as zarf, NVIDIA NIM, and Red Hat disconnected install.
jina-on-prem.py uses Python stdlib onlyHF_HUB_OFFLINE=1 + TRANSFORMERS_OFFLINE=1 enforced at runtimehttp(s) image or video URL when a request contains one - reachability is your network's decision, and --network none still serves every model. Only http/https and inline data: URLs are read, capped at 10 MB with an 8s budgetDockerfile.gpu (pytorch base, CUDA, FP16) and Dockerfile.cpu (python:3.11-slim)catalog.json deps field drives exact versions per modeldimensions to truncate embeddings to any supported sizeIf model dependencies are already installed:
python jina-on-prem.py serve --model jinaai/jina-embeddings-v5-text-nano --port 8080
python jina-on-prem.py serve --local-path /data/models/jina-v5-nano
jina-on-prem/
- jina-on-prem.py # CLI: bundle / deploy / serve / list
- models/
- catalog.json # 29-model registry with pinned deps
- docker/
- Dockerfile.gpu # GPU image (pytorch base, FP16)
- Dockerfile.cpu # CPU image (python:3.11-slim)
- download_model.py # Model download + patch script (build stage)
- server/
- app.py # FastAPI server: 5 API schemas
- requirements.txt # Server framework deps
- scripts/
- bootstrap-gcp.sh # one-shot GCP L4 builder provisioner
- pull-prebuilt.sh # pull GHCR image + save tar.gz for offline transport
- benchmark.py # throughput benchmark
- verify-offline.sh # prove an image serves with no network at all
Python
93.8%
Shell
6.2%
Air-gapped deployment toolkit for Jina AI models
15
stars
154
commits
Python
primary language
Aug 13, 2026
updated
On-prem deployment toolkit for Jina AI models. Bundle embedding, reranker, and reader models into self-contained Docker images that run fully offline.
New here? The Quick Start wiki page gets you to your first
/v1/embeddingsresponse in 5 minutes using a prebuilt image. Full tutorials, troubleshooting, and the model catalog live in the wiki.
![]()

flowchart TB
subgraph Phase1["Phase 1: BUNDLE - requires network"]
A["python jina-on-prem.py bundle"] --> B["Select model + runtime"]
B --> C["Docker build, weights + deps baked in"]
C --> D["docker save - .tar.gz"]
end
Phase1 -->|"USB / SCP / physical media"| Phase2
subgraph Phase2["Phase 2: DEPLOY - no network needed"]
E["docker load < model.tar.gz"] --> F["docker run -p 8080:8080"]
F --> G["Multi-schema API ready: OpenAI / Gemini / Cohere / Voyage"]
G --> H["Elasticsearch / Your App"]
end
./scripts/pull-prebuilt.sh jina-embeddings-v5-text-nano cpu
# produces jina-embeddings-v5-text-nano-cpu.tar.gz
# transfer it, then on the offline machine:
docker load < jina-embeddings-v5-text-nano-cpu.tar.gz
docker run -p 8080:8080 jina/jina-embeddings-v5-text-nano:cpu
On a Mac (Apple Silicon)? Prebuilt images are
linux/amd64only, so a plaindocker pullfails withno matching manifest for linux/arm64/v8.pull-prebuilt.shhandles this for you, falling back tolinux/amd64only when the registry has nothing for your architecture; if you pull by hand, add the flag to both commands:docker pull --platform linux/amd64 ...anddocker run --platform linux/amd64 .... It then runs under Rosetta emulation, correct but slow - fine for a first look, not for a benchmark. To develop against the API on a Mac, skip Docker and run the server directly:python jina-on-prem.py serveuses native arm64 PyTorch.
python jina-on-prem.py list # show all models
python jina-on-prem.py bundle # interactive picker
python jina-on-prem.py bundle --model jina-embeddings-v5-text-nano --cpu-only --yes
Need a builder machine? scripts/bootstrap-gcp.sh provisions one on GCP with Docker + NVIDIA Container Toolkit + the repo pre-cloned.
No repo, no scripts, no Python. Just Docker.
docker load < MODEL.tar.gz
docker run -p 8080:8080 jina/MODEL:cpu # CPU
docker run --gpus all -p 8080:8080 jina/MODEL:gpu # GPU
curl http://localhost:8080/health
Or via docker compose:
MODEL=jina-embeddings-v5-text-nano RUNTIME=cpu docker compose up -d
# for embed + rerank side-by-side:
docker compose -f docker-compose.multi.yml up -d
uv pip install openai requests
python examples/python_client.py
Drops in via OpenAI SDK with base_url="http://your-host:8080/v1".
29 models supported: embeddings (v5, v4, v3, v2), rerankers, readers, ColBERT, CLIP, VLM. 28 of them have prebuilt images; every model can be bundled from source. Headline picks:
| Model | Type | Modality | Params | VRAM | Prebuilt |
|---|---|---|---|---|---|
jina-embeddings-v5-text-nano | embedding | text | 239M | ~2GB | cpu / gpu |
jina-embeddings-v5-text-small | embedding | text | 677M | ~3GB | cpu / gpu |
jina-embeddings-v3 | embedding | text | 570M | ~3GB | cpu / gpu |
jina-embeddings-v5-omni-nano | embedding | multimodal | 1.04B | ~5GB | cpu / gpu |
jina-embeddings-v5-omni-small | embedding | multimodal | 1.74B | ~8GB | cpu / gpu |
jina-clip-v2 | embedding | multimodal | 865M | ~4GB | cpu / gpu |
jina-reranker-v3.5 | reranker | text | 0.6B | ~3GB | cpu / gpu |
jina-reranker-v3 | reranker | text | 597M | ~3GB | cpu / gpu |
Full catalog with all 29 models, VRAM, context windows, and licenses: Model Catalog wiki (auto-generated from models/catalog.json).
CC-BY-NC-4.0 models require a commercial license for production use. Contact Elastic sales.
:gpu-opt)The default :gpu server runs one forward pass at a time, so concurrent clients queue behind each other. The :gpu-opt tags add a server-side dynamic batcher: one GPU worker coalesces concurrent requests into length-sorted, token-budgeted batches, so clients can send one input at a time and still keep the GPU busy.
Published for the 16 embedding models. Rerankers, ColBERT, reader and VLM models have :cpu and :gpu only:
docker run --gpus all -p 8080:8080 \
ghcr.io/jina-ai/jina-on-prem/jina-embeddings-v3:gpu-opt
Same weights, same API, same output as :gpu — verified per model, not assumed: on eight of the sixteen the vectors are identical to the last bit, and on the rest they agree to cosine ≥ 0.9998. Still multi-task: every task (retrieval/text-matching/clustering/classification) behaves as it does on :gpu. fp16 by default, matching the stock :gpu dtype.
Where the batcher earns its keep, and where it does not:
input arrays — no gain. The client is already batching, and :gpu is the simpler choice.Whichever image you run, ask for encoding_format: "base64" — same numbers, and up to 2.6× the throughput on bulk requests, because a 768-dimension vector goes out as one string instead of 768 JSON floats. The OpenAI SDK does this by default. Tunable via JINA_BATCH_TOKENS, JINA_BATCH_WAIT_MS and JINA_DTYPE, with defaults baked in.
Which image for which traffic, the measured :gpu / :gpu-opt comparison, and per-model image sizes: Sizing & Hardware.
The server speaks five schemas on the same port simultaneously:
# Jina / OpenAI / Voyage AI
curl http://localhost:8080/v1/embeddings \
-H 'Content-Type: application/json' \
-d '{"input": ["Hello world"]}'
# Cohere
curl http://localhost:8080/v2/embed \
-H 'Content-Type: application/json' \
-d '{"texts": ["hi"], "input_type": "search_query"}'
# Google Gemini
curl 'http://localhost:8080/v1/models/MODEL:embedContent' \
-H 'Content-Type: application/json' \
-d '{"content": {"parts": [{"text": "hi"}]}, "taskType": "RETRIEVAL_QUERY"}'
# Reranker
curl http://localhost:8080/v1/rerank \
-H 'Content-Type: application/json' \
-d '{"query": "best embedding model", "documents": ["..."], "top_n": 2}'
# Reader / VLM models generate text instead of vectors
curl http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"messages": [{"role": "user", "content": "hi"}]}'
Tasks (retrieval, text-matching, classification, ...), matryoshka truncation (dimensions: 128), and multimodal inputs (omni / clip / v4 / vlm models): see the API Reference wiki.
Elasticsearch inference service drop-in: Elasticsearch integration.
Optional offline entitlement signal: a signed, expiring key that records a visible "expires on X" date against a deployment. Fully air-gapped (local HMAC check, no phone-home); issuing/renewing needs no image rebuild (key injected at run time).
python jina-on-prem.py keygen --sub acme-corp --days 90 # mint a 90-day key
docker run -e JINA_LICENSE_KEY=JINA-xxx.yyy -p 8080:8080 jina/MODEL:cpu
curl -s http://localhost:8080/health # shows license status; /health always open
Fail-open by default: a running deployment is never blocked. The default warn mode always serves - a missing/expired/invalid key only logs and shows in /health. Hard 403 blocking is opt-in via JINA_LICENSE_MODE=enforce (trials/POCs only), and even then an expired key survives a grace window. Compliance speed-bump, not DRM - the signing secret ships in the image, so this is an honest-system check rather than a lock. Details: Licensing wiki.
Two-phase model: bundle (Phase 1, connected) and deploy (Phase 2, offline). Same terminology as zarf, NVIDIA NIM, and Red Hat disconnected install.
jina-on-prem.py uses Python stdlib onlyHF_HUB_OFFLINE=1 + TRANSFORMERS_OFFLINE=1 enforced at runtimehttp(s) image or video URL when a request contains one - reachability is your network's decision, and --network none still serves every model. Only http/https and inline data: URLs are read, capped at 10 MB with an 8s budgetDockerfile.gpu (pytorch base, CUDA, FP16) and Dockerfile.cpu (python:3.11-slim)catalog.json deps field drives exact versions per modeldimensions to truncate embeddings to any supported sizeIf model dependencies are already installed:
python jina-on-prem.py serve --model jinaai/jina-embeddings-v5-text-nano --port 8080
python jina-on-prem.py serve --local-path /data/models/jina-v5-nano
jina-on-prem/
- jina-on-prem.py # CLI: bundle / deploy / serve / list
- models/
- catalog.json # 29-model registry with pinned deps
- docker/
- Dockerfile.gpu # GPU image (pytorch base, FP16)
- Dockerfile.cpu # CPU image (python:3.11-slim)
- download_model.py # Model download + patch script (build stage)
- server/
- app.py # FastAPI server: 5 API schemas
- requirements.txt # Server framework deps
- scripts/
- bootstrap-gcp.sh # one-shot GCP L4 builder provisioner
- pull-prebuilt.sh # pull GHCR image + save tar.gz for offline transport
- benchmark.py # throughput benchmark
- verify-offline.sh # prove an image serves with no network at all
Python
93.8%
Shell
6.2%