create-ragen-app instead; come back here when you want to understand and control what it did.
Prerequisites
Ensure the following are in place before you begin:- Docker and Docker Compose — all backing services run in containers.
- Node.js
^24.15.0 || >=26.0.0— required if you are running the applications outside of containers. This is a range, not a floor: 24.0–24.14 and the whole of 25 failnpm installon a transitive engine constraint. - 8 GB of RAM available to Docker to evaluate Ragen, 16 GB for a small production install. Most of that is headroom rather than steady-state: the containers a default
docker compose upstarts idle at about 900 MB together, and the rest covers document parsing under load and the applications themselves. What the stack actually costs breaks it down.
Manual deployment
1
Start the backing services
From the root of your cloned repository, bring up the full set of infrastructure services:This starts Postgres, Qdrant, Redis, and Docling in Docker. The applications themselves run on the host with hot reload.
To run every application in containers as well,
npm run ragen:up:everything builds and starts the web app, API, worker, and admin panel alongside all services.2
Install dependencies
Install Node.js dependencies across the entire monorepo:
3
Generate the Prisma client
The Prisma client is generated from the schema and is not committed to the repository. Nothing builds without it:
4
Run database migrations
Apply the Prisma schema to your database:This command reads
DATABASE_URL directly from the environment — make sure that variable is set before you run it.5
Seed the database
Populate the database with the default configuration that every new organization requires:This step is not optional — without it, organization creation fails. It reads configuration from
.env.local.6
Start the applications
Start each application in a separate terminal. There is no single root
dev command because these are separate processes with separate lifetimes:apps/worker is what makes an uploaded document searchable. It is not in docker-compose.yml — it runs on the host and drains the job queues in Redis. Without it, uploads succeed and then sit unparsed indefinitely. The worker validates its own environment at boot and exits with the missing variable named rather than starting in a broken state.apps/mcp is the fifth application, and it is optional: it exposes your assistants to MCP clients such as Claude Desktop and Cursor, and nothing in the web panel depends on it. It is a thin forwarder — no database, no Redis, no key of its own — but it does need apps/api reachable, because every tool call goes there with the caller’s own API key. Set RAGEN_MCP_PORT rather than PORT: three services in this repository read a bare PORT, and the root .env.local is shared by all of them. See MCP Server.Applications
Five applications ship in the repository. Three of them make a working install; the other two add surfaces you may not need.Services
Every service has a specific role in the stack. Removing one affects only the capabilities it provides.Redis is not optional on the default runtime, and it is the variable most often missed.
WORKER_RUNTIME defaults to bullmq, which puts the job queues in Redis: the worker drains them, and apps/web and apps/api write to them. An application that cannot reach Redis cannot enqueue a job, so an upload fails outright rather than being processed later. The Redis it points at must run maxmemory-policy noeviction — an evicting instance drops queue keys and the jobs go with them, which the worker refuses at boot rather than discovering in production. See Background jobs.What the stack actually costs
Measured on an idle stack, backing services only:
Two figures rather than one, because most of the total is optional. A plain
docker compose up starts neither Presidio container, so unless you asked the
installer for PII masking the containers cost you about 900 MB, not 1.9 GB.
Qdrant is the line that moves as you add documents — size that one against your
own corpus rather than against this table.
The Node applications run on top of this and are not in the table. In
development they are the larger half — each Next.js dev server is comfortably
larger than Postgres — which is why the machine recommendation above sits well
above the container total.
Trimming it further
With Docling and Presidio both out, the stack is Postgres, Redis and Qdrant —
under 150 MB idle.
Published container images
Building five images on every install is work nobody needs to repeat. Each release publishes them to GitHub Container Registry, amd64:v1.4.2), the minor series
(1.4), the exact commit (sha-…) and latest. Pin production to the sha-
tag — latest moves, and so does the minor series.
The images hold no per-install configuration. Everything they need comes from
the environment at runtime, which is what makes one published image correct for
every install; the configuration reference
lists what to supply.
Three of these serve the default job runtime, and only that.
ragen-web and ragen-api are BullMQ producers and ragen-worker is the
consumer, which is what WORKER_RUNTIME defaults to. A deployment that sets
WORKER_RUNTIME=temporal cannot use those three as published — the worker
exits at boot and the two producers throw on the first upload. ragen-admin
and ragen-mcp touch no queue and are unaffected. See
Durable execution for what
to run instead: the worker is an image swap, the producers are a rebuild.npm run ragen:up:everything still builds from source, which is what you
want while developing against a checkout. The published images are for
deployments that are not tracking main.Feature flags
Feature flags are environment variables that turn capabilities on or off. Set them in your.env.local file.
The following flags are off by default — set them to 1 to enable:
The following flag is on by default — set it to
0 to disable:
PII masking is configuration, not a flag
FEATURE_FLAG_PII_MASKING belongs in neither table above. Masking is on when
both PRESIDIO_ANALYZER_URL and PRESIDIO_ANONYMIZER_URL are set, and the
flag exists only to switch it off again:
1 enables nothing on its own. The two Presidio containers use
roughly 1 GB of RAM together and sit behind --profile pii, which is why this
is opt-in at all. See PII masking.
Multi-query expansion has no environment flag. It is a per-organization setting, defaulting to on, shown alongside document summaries and reranking under Organization → RAG settings. Those switches are read-only: a platform administrator sets them per organisation on the admin panel’s RAG Settings page.
Minimum environment
The variables below are the minimum required for a working Ragen installation. Copy this block into your.env.local and replace each placeholder with a real value.
.env.local
The model lines above are a sketch, and the route table is the part they leave out. Ragen calls providers itself through a route table that maps each model id to an upstream, and the table shipped in the repository names Azure, Bedrock, Vertex and Scaleway — providers a new installation has no credentials for. A key alone therefore is not enough: the routes have to name the provider that key belongs to.The least error-prone way to get all of it consistent — key, model ids, embeddings model and its vector size, and a matching route table — is to scaffold the install with
npx create-ragen-app, which asks which provider you have and writes the table for it. To wire it by hand instead, write your own file and point LLM_ROUTES_PATH at it.ENCRYPTION_PROVIDER, a persistent storage path or S3 credentials, and DOCLING_STRICT=1 if documents must not leave your network. The full variable reference — including which provider makes which variable mandatory — is in the configuration reference.
Running without internet access
Ragen’s architecture is designed to support fully air-gapped deployments: the model layer is decoupled behind a route table or a proxy of your choosing, and document parsing is local by default. Two honest caveats apply before you commit to this path. It is deployment work, not a flag. Serving a capable model on your own hardware means provisioning GPU capacity. Locally-served open models generally perform less well than commercial ones on complex questions — how much less depends on your documents and your questions, so measure it on your own material first. Container images are pulled at install time. After that, outbound traffic can be cut entirely, with updates delivered as images to your internal registry.Running in production
A local development setup is not suitable for production. Three areas require deliberate decisions before you expose Ragen to real users.Encryption
In local development Ragen starts with no key provider and stores message content in plaintext. A deployed one does not. A process needs an encryption provider unless it is plainly not a deployment:NODE_ENV=development or test, or TARGET_ENV set to local, test, e2e or ci. Anywhere else — including a build that simply never set TARGET_ENV — a missing provider stops it: apps/api exits, apps/web serves a blocking page, and every content write throws. That is deliberate — the alternative silently writes readable messages until somebody thinks to check.
Set ENCRYPTION_PROVIDER to one of the following values and supply the matching key material:
Leaving
ENCRYPTION_PROVIDER unset is not the same as having none: Ragen then auto-detects from whichever credentials are present, in the order Scaleway, KMS, local. Naming the provider is the readable choice.
To run a deployed environment without encryption anyway — during a migration, say — set ALLOW_UNENCRYPTED=1. It is a conscious opt-out, logged as a critical security event on every start. It does not rescue a provider that is configured but broken; see Encryption.
Verify encryption is active by creating a new thread after the change — if the API returns the thread without error and subsequent messages are readable, encryption is working correctly.
Storage
STORAGE_PROVIDER=local writes uploaded files to STORAGE_LOCAL_PATH (defaults to ./data/storage). Mount a persistent volume at that path, or a restart loses every uploaded document.
For production and multi-replica setups, use STORAGE_PROVIDER=s3, which works with any S3-compatible object store:
.env.local
docker compose --profile s3 up -d starts the bundled RustFS. The installer sets it up if you choose RustFS as the storage answer. See Storage.
Use
S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY — not AWS_-prefixed variables. The AWS_ prefix is reserved for real AWS Bedrock and KMS configuration, which a deployment can use at the same time as non-AWS S3 storage.Embedding model and vector dimensions
EMBEDDINGS_MODEL defaults to bge-multilingual-gemma2, which produces 3584-dimensional vectors. VECTOR_SIZE must match this value, or Qdrant rejects every upsert.
If you switch to a different embedding model, update VECTOR_SIZE accordingly:
VECTOR_SIZE has one default — 3584, the default model’s — and Ragen does
not infer it from any other model name. Setting EMBEDDINGS_MODEL and leaving
VECTOR_SIZE alone is therefore the mismatch above rather than a smaller
configuration: change one and you must change the other. The minimum
environment earlier on this page sets both together for that reason.