What IronRAG provides
- Typed knowledge graph. Documents are decomposed into entities, typed relationships, and chunk-level evidence references. Retrieval combines vector, lexical, graph-traversal, and technical-fact lanes; the answer pipeline returns citations to the underlying chunks.
- Native MCP server. 23 tools across documents, graph, web ingest, and grounded
ask. Connect it to MCP-compatible agents and clients such as Claude Desktop, Claude Code, Cursor, Codex, VS Code with Continue / Cline / Roo, Zed, OpenClaw, Hermes, Lobe-style chat agents, or a custom HTTP MCP client. Tools are scoped per IAM token. - Provider-agnostic AI runtime. Eight LLM providers ship in the catalog — OpenAI, DeepSeek, Qwen (DashScope-intl), GPTunnel, OpenRouter, RouterAI, MiniMax, Ollama. Five required profiles (
extract_graph,embed_chunk,query_compile,query_answer,agent) and optional multimodalextract_textcover every model-backed stage without duplicate query-vector, rerank, or vision bindings. A vector rebuild utility safely applies embedding-space changes. - USD cost catalog. Every binding stores prices in USD. Per-call billing rows are written for every LLM request and rolled up per document and per query in the UI.
- Multi-tenant IAM. Principals, scoped tokens (system / workspace / library), and permission groups gate every API surface. Audit log captures resource access.
- Self-hosted runtime. The Docker Compose stack includes PostgreSQL with pgvector, Redis, backend, worker, and frontend services. Helm chart available for Kubernetes.
- Code-aware ingest. 15-language tree-sitter AST parsing. Native parsers for JSON / YAML / TOML / CSV / XLSX. Technical-fact extraction for paths, params, endpoints, env vars, and error codes.
- CPU-first recognition. Docling CPU runtime is baked into the backend image; PDF / DOCX / PPTX layout extraction, raster-image OCR, and embedded document-picture OCR run without a GPU. Stored PDFs are extracted through resumable page-range checkpoints, and image OCR can be switched per library to the multimodal Document Understanding (
extract_text) profile. - Restart-safe processing. Long document jobs keep durable extraction units, reusable embedding / graph outputs, and lease-guarded finalization, so stack restarts or transient network breaks resume from the last completed unit instead of discarding hours of work.
- Durable assistant turns. UI answer streaming is an activity channel over the same persisted query execution; if the browser or proxy drops the stream after work starts, the frontend reloads the completed session result instead of submitting the question again. LLM debug snapshots are stored per execution for post-reload inspection.
- Backup and restore. Streaming
tar.zstarchive with selective sections (catalog only, with blobs, with graph). Restore to the same or a different deployment. - Pluggable source connectors. Push content into IronRAG from a vendor system via a small Python adapter. Build your own on the IronRAG Connector Template, or run the BookStack connector and Confluence connector (pages + attachments + images, periodic poll + webhook intake).
Quick start
Install or update:
curl -fsSL https://raw.githubusercontent.com/mlimarenko/IronRAG/master/install.sh | bash
The installer is an interactive wizard: it inspects the host (CPU + RAM),
recommends a resource profile (per-service memory/CPU caps and ingest
parallelism), and prompts step by step for the port and optional admin
bootstrap, then shows a review screen before writing anything. Provider keys
come from the typed environment namespace below or are configured in the UI;
the installer carries no provider-name table. On a re-run it preserves the existing
.env secrets and tuned caps, while official IronRAG image pins are advanced
to the selected release tag.
The installer also runs fully non-interactively. With no terminal (the piped
curl | bash form), or with --yes / --non-interactive, it takes every answer
from flags, environment variables, and existing .env values. Answer precedence
is flags > env > prompt for non-secret values; secrets (admin password, provider
API keys) are accepted via environment variables or a pre-seeded .env only —
never as flags, since argv is visible to other processes and shell history. Run
./install.sh --help for the full flag and environment-variable list, or
--plan-only to print the detected profile without writing or deploying anything.
Non-interactive example (CI / Ansible):
curl -fsSL https://raw.githubusercontent.com/mlimarenko/IronRAG/master/install.sh -o install.sh
IRONRAG_PORT=8080 \
IRONRAG_PROFILE=small \
IRONRAG_AI_PROVIDER_API_KEYS_JSON_B64="$(printf '%s' '{"provider-alpha":"secret-value"}' | base64 | tr -d '\n')" \
bash install.sh --non-interactive
Or from source:
git clone https://github.com/mlimarenko/IronRAG.git
cd IronRAG
cp .env.example .env # add IRONRAG_AI_PROVIDER_API_KEYS_JSON_B64
docker compose up -d
Open http://127.0.0.1:19000, create an admin account, upload a document, and ask a question.
Telemetry. Fresh installations export nothing. Local structured logs stay available, while OpenTelemetry export requires both
IRONRAG_OTEL_ENABLED=trueand an explicitOTEL_EXPORTER_OTLP_ENDPOINTfor a collector you control. A stableironrag.deployment.idis derived only after that opt-in. Log export remains separately disabled because logs may contain document or query content.
Multi-provider configuration
Set as many provider keys as you need in one JSON map, encode that map with
standard base64, and store the encoded value in .env. Credentials
auto-register on the next restart, and discovered catalog models become
selectable in the admin UI's canonical binding profiles. Provider-kind keys are
preserved exactly without case folding or separator conversion, so a new
catalog provider does not require a code change.
The base64 envelope prevents dotenv/Compose from reinterpreting $, #,
quotes, backslashes, or Unicode inside a credential. Credential strings are
preserved byte-for-byte, including leading or trailing whitespace. The map is
bounded to 256 entries and 1 MiB of decoded JSON; each exact provider kind is
at most 128 UTF-8 bytes and each credential at most 64 KiB.
This convention replaces the former provider-specific
IRONRAG_<PROVIDER>_API_KEY variables. Move those entries into the JSON map
before upgrading;
startup and install.sh reject malformed or removed names instead of silently
guessing an alias.
IRONRAG_CREDENTIAL_MASTER_KEY=<canonical-base64-32-byte-random-key>
# Optional; defaults to `default` for backwards compatibility.
IRONRAG_CREDENTIAL_MASTER_KEY_ID=current-2026
# Configure only during rotation; key IDs must be sorted and unique.
IRONRAG_CREDENTIAL_PREVIOUS_MASTER_KEYS=old-2025=<base64-key>,old-2026=<base64-key>
# Keep false during the first mixed-version upgrade; enable in a second rollout.
IRONRAG_CREDENTIAL_ENCRYPTION_WRITE_ENABLED=false
IRONRAG_AI_PROVIDER_API_KEYS_JSON_B64=<standard-base64-of-compact-JSON-object>
IRONRAG_CREDENTIAL_MASTER_KEY is a dedicated at-rest encryption key for
row-bound v3 envelopes in ai_account.api_key and outbound webhook signing
secrets. Each v3 envelope authenticates a validated key ID. Generate 32 random
bytes (for example, openssl rand -base64 32 | tr -d '\n'), store the result in
your secret manager, and back it up separately from PostgreSQL. Never derive it
from the database password. A deployment may start without the key for
legacy/read-only compatibility. Creating or updating a non-empty stored
credential fails closed unless both the key is configured and
IRONRAG_CREDENTIAL_ENCRYPTION_WRITE_ENABLED=true.
First upgrade to encrypted credentials
The write gate defaults to false in the application, .env.example, Docker
Compose, and Helm. This is intentional: an older API or worker cannot read new
v3 envelopes. install.sh sets the gate to true only when it creates a fresh
.env, because a fresh deployment has no older processes.
Use two separate rollouts for an existing installation:
- Back up the database and master key. Distribute the active key to every API,
worker, and startup process, but keep
IRONRAG_CREDENTIAL_ENCRYPTION_WRITE_ENABLED=false. Deploy the dual-reader release without changing provider or webhook credentials. Plaintext and old envelopes remain readable; new encrypted writes stay blocked. - Verify every API and worker replica runs the dual-reader release. Check
each pod/container image digest and its
/v1/versionresponse; do not rely on a load-balanced response from only one replica. - In a second rollout, set the write gate to
true, wait for every API and worker replica to restart and become ready, then resume credential changes. - Run the credential migration first without
--apply, apply it, and run the dry check again. Do not retire legacy support or key material while rows or retained backups still require them.
Helm automatically restarts API, worker, and startup pods when its inline
ConfigMap or Secret changes. When runtimeSecret.existingSecret is used, Helm
cannot checksum that external object: change runtimeSecret.restartNonce on
every external Secret update, including both key-rotation rollouts.
Three-phase master-key rotation
Key IDs are 1-32 lowercase letters, digits, ., _, or -, starting with an
alphanumeric character. IRONRAG_CREDENTIAL_PREVIOUS_MASTER_KEYS accepts up to
eight id=canonical-base64-key entries with unique IDs, no whitespace, and
strictly sorted IDs. Previous entries are decrypt/rewrap-only.
Rotate in three phases so replicas from adjacent rollouts can decrypt one another's writes:
-
Distribute, do not switch. Keep the old key active and add the new key to
IRONRAG_CREDENTIAL_PREVIOUS_MASTER_KEYSon every API and worker. Complete the rollout and verify every replica has the bridging keyring. -
Switch active. Make the new key active and keep the old key in the previous-key map. During the rolling overlap, old-config replicas know the new key as previous and new-config replicas know the old key as previous.
-
Rewrap, verify, retire. Inventory and rewrap legacy and old-key rows:
docker compose run --rm backend ironrag-maintenance migrate credential-secrets docker compose run --rm backend ironrag-maintenance migrate credential-secrets --apply docker compose run --rm backend ironrag-maintenance migrate credential-secretsRemove the old key only after the final inventory reports no remaining rows, every replica uses the new active key, and backups containing old-key envelopes have aged out. Roll out the reduced keyring everywhere. Unknown v3 key IDs fail closed, so losing or retiring a key early makes matching values unreadable.
| Provider | Chat | Vision | Embedding | Notes |
|---|---|---|---|---|
| OpenAI | ✅ | ✅ | ✅ | Direct API |
| DeepSeek | ✅ | — | — | Very low cost; may be slower than other APIs; no native vision/embeddings |
| Qwen / DashScope-intl | ✅ | ✅ | ✅ | Cost is close to DeepSeek; API is often faster; strong chat/vision/embedding lane |
| GPTunnel | ✅ | ✅ | ✅ | Router provider: many upstream model families behind one key |
| OpenRouter | ✅ | ✅ | ✅ | Router provider: many upstream model families behind one key |
| RouterAI | ✅ | ✅ | ✅ | Router provider: many upstream model families behind one key |
| MiniMax | ✅ | ✅ | — | Direct API or Token Plan subscription key |
| Ollama | ✅ | ✅ | ✅ | Fully local, air-gapped, GPU optional |
| LiteLLM | ✅ | ✅ | ✅ | Self-hosted OpenAI-compatible gateway: add as an OpenAI-compatible account with your LiteLLM base URL; proxies any upstream model family |
Configure exactly five required profiles under Admin → AI → Bindings:
extract_graph, embed_chunk, query_compile, query_answer, and agent.
Add the optional multimodal extract_text profile only when Document
Understanding is needed. Bindings are scoped to instance, workspace, or library
— a workspace can override the instance default for a single profile.
Recognition note: the raster-image engine named vision uses the active multimodal extract_text profile. Switch a library to the docling engine to OCR images locally and make no visual-model calls. Document Understanding is optional, but selecting the vision engine without a compatible extract_text binding fails loudly.
Common deployments
- Internal knowledge bot. A company library is fed from BookStack, Confluence, Google Drive, SharePoint, or a recursive web crawl. Engineering, support, and policy questions are answered through the UI assistant or via MCP, with citations linking back to the original chunks.
- Public portal / helpdesk assistant. A self-hosted assistant fronts your published documentation, release notes, and runbooks. The MCP
asktool drives a chat widget; queries do not leave the host infrastructure. - Engineering / coding agent. AST-parsed source trees and extracted technical facts (endpoints, env vars, config keys, error codes) give an MCP-connected agent structured context for architecture and "what changed" questions across releases.
- Personal long-term memory. A single-tenant deployment used as a long-lived second brain — papers, notes, code snippets, web clippings — queried from any MCP client. The graph grows as the library grows.
- On-prem / regulated AI. Bind every purpose to Ollama for an air-gapped runtime. The deployment is inert outside its own network: no provider telemetry, IAM-scoped tokens, full backup archive for retention.
Tech stack
| Layer | Technology |
|---|---|
| Backend | Rust 1.97, axum 0.8, tokio 1.52, SQLx 0.8, tower 0.5 |
| Frontend | React 19.2, Vite 8.0, TypeScript 6.0, Tailwind 4.3, shadcn/ui |
| Frontend build/runtime | Node 26.4.0 + npm 11.17.0 (build), Nginx 1.30 (static serving) |
| Graph rendering | Sigma.js 3 + Graphology 0.26 (WebGL, Web Worker layout) |
| Database | PostgreSQL 18 (pgvector image) |
| Knowledge-plane search | pgvector, PostgreSQL full-text search, pg_trgm |
| Cache / job queue | Redis 8.8 |
| Document recognition | Docling CPU runtime, native parsers, tree-sitter (15 languages) |
| MCP | Streamable HTTP MCP server (2025-11-25), 23 tools |
| Deployment | Docker Compose, Helm chart |
Pipeline overview
- Ingest. Files, web pages, and API / MCP uploads enter the recognition router (
extract_text), stored PDFs are checkpointed by page range, source text is split into structured chunks (chunk_content+ structured-block preparation), and persisted to PostgreSQL. - Build memory. Each chunk is embedded (
embed_chunk), scanned for technical literals (extract_technical_facts), and processed byextract_graphto write entities, typed relations, and evidence references. - Query. A query session compiles the user request into typed IR (
query_compile); vector, lexical, graph-traversal, and technical-fact lanes retrieve concurrently; the answer router selects between a grounded answer (query_answer) and a clarification, runs the verifier, and persists citations to the response. - Provider routing. Every LLM call resolves through the binding for its purpose. Switching
query_answerfrom OpenAI to a local Ollama model is a binding change at the matching scope (instance / workspace / library).
For deep dives:
| Topic | English | Russian |
|---|---|---|
| Architecture overview | docs/en/README.md | docs/ru/README.md |
| Ingestion pipeline | docs/en/PIPELINE.md | docs/ru/PIPELINE.md |
| MCP server & tools | docs/en/MCP.md | docs/ru/MCP.md |
| IAM & tokens | docs/en/IAM.md | docs/ru/IAM.md |
| Credential encryption | docs/en/CREDENTIAL-ENCRYPTION.md | docs/ru/CREDENTIAL-ENCRYPTION.md |
| CLI reference | docs/en/CLI.md | docs/ru/CLI.md |
| Frontend architecture | docs/en/FRONTEND.md | docs/ru/FRONTEND.md |
| Frontend transport (TLS/QUIC) | docs/en/FRONTEND-TRANSPORT.md | docs/ru/FRONTEND-TRANSPORT.md |
| Capacity planning | docs/en/CAPACITY-PLANNING.md | docs/ru/CAPACITY-PLANNING.md |
| Webhooks | docs/en/WEBHOOK.md | docs/ru/WEBHOOK.md |
| Benchmarks | docs/en/BENCHMARKS.md | docs/ru/BENCHMARKS.md |
| Changelog | CHANGELOG.md | — |
Other deployment options
All variants live in the single docker-compose.yml, selected by env and one
profile (see the file header for the full list):
# With S3-compatible storage (bundled s4core), via the `s4` profile
COMPOSE_PROFILES=s4 \
IRONRAG_CONTENT_STORAGE_PROVIDER=s3 \
IRONRAG_DEPENDENCY_OBJECT_STORAGE_MODE=bundled \
IRONRAG_CONTENT_STORAGE_TOPOLOGY=shared_cluster \
docker compose up -d
# Local source build for development
IRONRAG_BACKEND_IMAGE=ironrag-backend:local \
IRONRAG_FRONTEND_IMAGE=ironrag-frontend:local \
docker compose build backend frontend
IRONRAG_BACKEND_IMAGE=ironrag-backend:local \
IRONRAG_FRONTEND_IMAGE=ironrag-frontend:local \
docker compose up -d
# Larger host (24-32 GiB): raise the per-role memory caps
IRONRAG_DB_MEMORY_LIMIT=6144M \
IRONRAG_BACKEND_MEMORY_LIMIT=4096M \
IRONRAG_WORKER_MEMORY_LIMIT=4096M \
docker compose up -d
The default Compose stack keeps ingest conservative on small swapless hosts:
IRONRAG_INGESTION_MAX_PARALLEL_JOBS_GLOBAL=4,
IRONRAG_INGESTION_MAX_PARALLEL_JOBS_PER_WORKSPACE=2, and
IRONRAG_INGESTION_MAX_PARALLEL_JOBS_PER_LIBRARY=1. Raise those only together
with worker memory, database connection budget, and provider concurrency.
Provider calls are bounded per endpoint by default (16 total, 4 reserved
for query turns) and fail with a typed overload error after a 30-second permit
wait. Set both values to 0 only when unlimited upstream fan-out is deliberate.
Helm (Kubernetes):
helm upgrade --install ironrag charts/ironrag \
--namespace ironrag --create-namespace \
--set-string app.providerSecrets.provider_alpha="${PROVIDER_API_KEY}" \
--wait --timeout 20m
By default the chart deploys the API, worker, and web images with the
v<appVersion> image tag derived from Chart.appVersion. Override
api.image.tag, worker.image.tag, and web.image.tag only when pinning
a different published image, for example --set web.image.tag=v0.5.9.
Bundled dependencies (same pins as Docker Compose): pgvector/pgvector:pg18
for PostgreSQL and redis:8.8 for Redis. Override via
dependencies.postgres.image.* and dependencies.redis.image.* when needed.
No comments yet
Be the first to share your take.