Running Fully Local & Air-Gapped
indx ships cloud defaults out of the box, but it can also run the entire pipeline on your own hardware with nothing leaving the network.
This guide walks the air-gapped path end to end: pick an offline path, pre-stage model weights, choose a local store, produce a self-contained .indx, and record an audit-grade manifest.
There are two offline paths:
- Zero-dependency offline core: pass
--offline. This usesplaintext,none,none,hash,jsonl, and.indx. It needs no extras and is best for smoke tests, plain text, Markdown, and policy-constrained machines. - Local semantic profile: install local extras and select
docling,ollama:qwen2.5,bge-m3, and a local store. This gives stronger parsing and semantic retrieval, but requires staged model weights.
Why the local profile is offline
Section titled “Why the local profile is offline”A bare zero-config run (indx <dir> --out <dir>) uses the cloud defaults: docling + openai:gpt-5-mini + openai:text-embedding-3-small (dim 1536) + qdrant. That run needs an OPENAI_API_KEY.
The local profile is opt-in instead. Select it one of two ways:
pip install "indx[local]", or- name the local backends explicitly through flags or
indx.toml.
Once selected, every value is local-capable, so the run never touches the internet.
| Slot | Zero-dependency --offline | Local semantic profile | Where it runs |
|---|---|---|---|
| Parser | plaintext | docling | Local — no API key, no network |
| LLM | none | ollama:qwen2.5 | Local, or disabled |
| VLM | none | none | No vision calls by default |
| Embedder | hash | bge-m3 (dim 1024) | Local |
| Store | jsonl | qdrant embedded or jsonl | Local on-disk |
| Output | .indx | .indx | Local self-contained archive |
On top of the local profile, the core install ships zero-dependency fallbacks: a plaintext parser, a jsonl store, the none VLM, and the .indx + jsonl writers. Even a bare pip install indx can complete a full run offline.
Step 1 — Pick the offline path
Section titled “Step 1 — Pick the offline path”For the core path, no installation beyond pip install indx is required:
indx ./docs --out ./ai-ready --offline--offline fills only slots you did not set explicitly, so you can override one piece:
# Offline core, but use Docling if its extra is installed.indx ./docs --out ./ai-ready --offline --parser doclingUse the local semantic profile when you need better document parsing and semantic embeddings.
On a machine that still has internet (your build/staging box), install the curated offline bundle:
pip install "indx[local]"indx[local] is the one-line install of the recommended air-gapped stack. It is aliased as indx[defaults], and both resolve to the same local bundle. The bundle includes:
- The Docling parser
- The local Ollama LLM client
- The local embedding runtime (FlagEmbedding / sentence-transformers + Torch)
- The Qdrant client
You can also assemble the stack yourself — for example, using the no-DB JSONL store and skipping the Qdrant client entirely. Install the pieces explicitly:
# Minimal offline floor: docling + local bge-m3 + jsonl no-DB storepip install "indx[docling]" "indx[bge]"The bare core (pip install indx) already includes the jsonl store and .indx/jsonl writers, so the command above gives you a complete offline pipeline with no database installed.
Step 2 — Pre-stage model weights for offline boxes
Section titled “Step 2 — Pre-stage model weights for offline boxes”An air-gapped target has no internet, so the LLM, embedder, and parser model weights must be present before you disconnect. Stage them once on a connected machine, then copy the caches across.
Pre-pull the Ollama model
Section titled “Pre-pull the Ollama model”The local profile LLM is ollama:qwen2.5, served by a local Ollama daemon. Pull the model while online:
ollama pull qwen2.5On the air-gapped box, run an Ollama daemon. Then either pull from an internal mirror or copy the Ollama model directory (~/.ollama/models on Linux/macOS) from the staging machine.
indx talks only to the local Ollama endpoint. It never reaches out to a model registry itself.
Pre-download the embedder and parser weights
Section titled “Pre-download the embedder and parser weights”The local profile embedder bge-m3 and the Docling parser download model weights from Hugging Face on first use. Warm those caches while online, then transport them:
# Trigger downloads once on a connected machine, then copy the caches.export HF_HOME=/srv/indx-models/hf # parser + embedder weights# Name the local backends so the warm-up exercises local weights, not the cloud defaults.indx ./sample-docs --out ./warmup --embedder bge-m3 --llm ollama:qwen2.5On the offline box, point the same environment variables at the copied caches and enable offline mode so the libraries never attempt a network fetch:
export HF_HOME=/srv/indx-models/hfexport HF_HUB_OFFLINE=1 # fail fast instead of reaching outexport TRANSFORMERS_OFFLINE=1Step 3 — Choose your store: zero-dependency floor vs. embedded Qdrant
Section titled “Step 3 — Choose your store: zero-dependency floor vs. embedded Qdrant”Both local store options keep vectors on disk and never contact a server. Pick based on corpus size.
JSONL — the absolute zero-dependency floor
Section titled “JSONL — the absolute zero-dependency floor”The jsonl store ships in the core and needs no database and no extra. It writes vectors as newline-delimited records, and its similarity search is a brute-force linear scan.
That is fine for small and medium corpora, and it maximizes portability inside the .indx archive.
indx ./docs --out ./ai-ready --store jsonlThis is the most defensible choice for an air-gapped deployment. There is nothing to install, nothing to operate, and the resulting archive is fully self-contained.
Qdrant in embedded / local mode
Section titled “Qdrant in embedded / local mode”Qdrant’s embedded mode runs in-process against a local on-disk path, with no server. You keep local-first guarantees and get a real ANN index that scales to larger corpora. If you later grow into a self-hosted server, the same client code points at it.
indx ./docs --out ./ai-ready --store qdrant| Store | Search | Best for | Network |
|---|---|---|---|
jsonl (no DB) | Brute-force linear scan | Small/medium corpora; maximum portability | None |
qdrant (embedded) | ANN index, on-disk | Larger corpora; same code scales to a server | None (embedded) |
Step 4 — Produce a self-contained .indx with vectors inline
Section titled “Step 4 — Produce a self-contained .indx with vectors inline”The default output writer is .indx, a Zip container holding manifest.json, the index.json knowledge graph, JSONL chunk shards, optional vector blobs, and enrichments.
When built with the JSONL store, vectors are stored inline. The archive is then completely self-contained: it re-opens and answers queries with no external service.
# Fully offline build → portable, self-contained archiveindx ./docs --out ./ai-ready --store jsonl
# Sanity-check structure and retrieval, still offlineindx inspect ./ai-ready/handbook.indxindx query ./ai-ready/handbook.indx "onboarding checklist"The same flow from the SDK:
from indx import DirectoryPipeline, KnowledgeSpace
space = DirectoryPipeline(store="jsonl").run("./docs", "./ai-ready")print(space.stats)
# Re-load later on any machine, no re-processing, no networkspace = KnowledgeSpace.load("./ai-ready/handbook.indx")hits = space.search("onboarding checklist", k=5)Because the archive carries the embedder’s name and dim (1024 for bge-m3) in its manifest, a consumer can detect a model/dimension mismatch before querying. See The .indx archive and index.json for the on-disk layout.
Step 5 — Capture the reproducibility manifest for audit
Section titled “Step 5 — Capture the reproducibility manifest for audit”Every run records its provenance into the .indx manifest:
- The producing build (
tool_version) - The schema version (
indx_version) - The chosen slot backends
- The embedder
nameanddim - Per-member checksums
This is what makes a knowledge space auditable and re-creatable. Given the same inputs, config, and model versions, a run is reproducible, and the manifest tells you exactly how each space was produced.
// manifest.json (illustrative excerpt){ "indx_version": "1.0", "tool_version": "indx 0.4.2", "slots": { "parser": "docling", "llm": "ollama:qwen2.5", "vlm": "none", "embedder": "bge-m3", "store": "jsonl", "output": "indx" }, "embedder": { "name": "bge-m3", "dim": 1024 }}For a fully byte-stable index.json, seed any randomness and pin model identifiers. See Reproducibility for the full recipe (seeding, deterministic serialization, and golden-file verification).
The safe pattern if a cloud component is ever introduced
Section titled “The safe pattern if a cloud component is ever introduced”If policy later allows a single cloud backend — say, a hosted LLM for higher-quality enrichment — keep egress controlled by inserting a redaction stage before Enrich.
Redaction is a first-class extension point, so sensitive content can be stripped before any egress-capable component sees it.
Stages obey a uniform run(ctx: SpaceContext) -> SpaceContext contract and communicate only through the shared SpaceContext. A redaction stage therefore drops cleanly into the ordered pipeline:
01 Walk → 02 Parse → 03 Chunk → 04 Relate → [Redact] → 05 Enrich → 06 Embed+PackThis keeps Walk, Parse, Chunk, and Relate fully local. The only stage that could egress then receives already-sanitized text. See Custom stages for how to author and insert one, and Enrichment with LLM/VLM for the enrichment slot itself.
Checklist for an air-gapped deployment
Section titled “Checklist for an air-gapped deployment”- Install
indx[local](orindx[docling]+indx[bge]for the no-DB floor) on a connected staging box. -
ollama pull qwen2.5; copy~/.ollama/modelsto the target. - Warm and copy the Hugging Face cache (
HF_HOME); setHF_HUB_OFFLINE=1/TRANSFORMERS_OFFLINE=1on the target. - Choose
--store jsonl(zero-dependency floor) or embedded--store qdrant. - Build →
inspect→queryentirely offline; confirm no outbound traffic. - Confirm
indx.tomlnames only local backends — noopenai/anthropic/hosted-Qdrant. - Archive the
.indxplus its manifest for audit and reproducibility.
Related
Section titled “Related”- Choosing a store — JSONL vs. Qdrant vs. the rest.
- Reproducibility — seeding, deterministic output, golden files.
- Extras reference — every
pip install "indx[...]"target. - Configuration guide and Configuration reference — pinning slots in
indx.toml. - The .indx archive — the self-contained, versioned format.