Use cases
Air-gapped LLM deployment with Ragmux
Run an OpenAI-compatible gateway with retrieval fully inside a network with no outbound internet access, using local model servers and one self-contained PostgreSQL database.
Updated: Last reviewed: Verified against Ragmux 0.5.0
“Air-gapped” and “on-premise” usually mean the same underlying requirement stated two ways: nothing about a request, a document or an answer is allowed to leave a network boundary the operator controls, whether that boundary is a literal physical air gap or a private cloud VPC with no path to the public internet. Ragmux’s architecture — one binary, one database, and every provider connection pointed wherever the operator chooses — was never built assuming a cloud model provider is reachable, which makes it a workable fit for that requirement without any special “air-gapped mode” to turn on.
What actually has to be local
An AI gateway is only as air-gapped as the models behind it. Ragmux’s own translation
layer works the same whether a connection’s provider_type is openai or ollama — the
gateway doesn’t know or care whether a request is about to leave the network, only that
it’s forwarding to whatever base_url the connection names. That means the deployment
decision that actually determines whether this works isn’t about Ragmux at all: it’s
whether a chat model and an embedding model are both running somewhere reachable inside
the isolated network. ollama and custom_openai (for vLLM, LM Studio, or any other
server speaking the OpenAI Chat Completions format) are the two provider types built for
exactly that — everything downstream of the model connection, including retrieval,
budgets and the audit log, works identically once that piece is in place.
The same local-only constraint applies to reranking, if a store uses it: cohere and
voyage rerank backends call an external API and need the same outbound access a cloud
model provider would, so they’re not available inside an isolated network. The llm
backend — reranking with the project’s own chat model, which is already local in this
deployment shape — has no such requirement, and is the one to reach for here.
The private-upstream guard exists for the opposite reason, and gets in the way here
Ragmux refuses, by default, to connect to a base_url that resolves to a loopback,
link-local or private address — a deliberate SSRF guard against a dashboard editor
pointing the gateway at an internal host it shouldn’t reach. In an air-gapped deployment,
“internal host” is the whole point: the local Ollama or vLLM server is supposed to be a
private address. PRIVATE_UPSTREAM_ALLOWLIST is what reconciles the two — naming the
specific hostnames that may resolve privately (ollama, host.docker.internal, a Compose
service name) without disabling the guard for every other connection the way
ALLOW_PRIVATE_UPSTREAMS=true would. The right move for most air-gapped installs is the
allowlist, not the blanket flag: it keeps the guard working for any connection an editor
adds later that shouldn’t be allowed to reach an internal address.
What crosses the air gap once, and what never does
The one moment this deployment shape genuinely needs the outside world is before it
becomes air-gapped at all: pulling the container image and verifying its signature.
Images from v0.3.1 onward are signed with cosign in keyless mode, verified against
Sigstore’s Fulcio service through the release workflow’s GitHub OIDC identity — which
needs network access to check. The
practical sequence is to do that verification on a connected host, then carry the
verified image (ideally pinned to the digest cosign verify reports, so a later tag move
can’t change what gets deployed) across the boundary with docker save and docker load
or an equivalent internal registry mirror. Nothing about running the gateway afterward
needs that connection again: SECRET_KEY encrypts credentials locally, the database is
one self-contained PostgreSQL instance, and — per the exhaustive list in
Configuration — there is no environment variable for a telemetry,
license-check or update-check endpoint anywhere in Ragmux’s configuration surface. The
dashboard itself is built to the same standard: it runs under a Content-Security-Policy
strict enough, in the project’s own words, that it “makes no third-party requests” at
all.
Data residency, and what it doesn’t mean
“On-premise AI gateway GDPR” is usually a data-residency question at heart: can an
organization keep model traffic, and whatever those requests are grounded in, inside
infrastructure it controls rather than sending it to a provider’s cloud. Running Ragmux
air-gapped answers that directly — documents are stored as bytea in your own
PostgreSQL, embeddings live in the same database, provider credentials are AES-256-GCM
encrypted with a key you hold, and request logs never leave the instance unless an admin
exports them. What it does not do is substitute for a compliance program: Ragmux carries
no GDPR, SOC2 or HIPAA certification of its own, because it is AGPL-3.0 software
published without a vendor behind it to go through that process — a fact worth being
plain about rather than letting “self-hosted” imply “certified.” The audit log (every
create, update, delete, login and key rotation, with actor, target and IP, exportable as
NDJSON) is the kind of evidence a compliance review would want to see; it is not itself
proof of compliance with anything.
What this deployment shape gives up
A single Ragmux instance on an isolated network gains nothing from the parts of the
architecture built around cloud reachability. cosign verify needs to happen before the
air gap, not continuously against it. A route can hand a failed
request to its next target, but that only helps when a second target is reachable — a
local model server going down is not covered by switching to a hosted one, because there
usually isn’t a hosted one reachable to switch to. And an organization
relying on this shape for a genuinely isolated network should plan its own process for
getting security patches in: the gateway has no update-check mechanism to tell an
operator a new release exists, by design, since that would itself be an outbound call the
environment doesn’t allow.
A worked example: a hospital network’s internal assistant
Take a concrete shape of this: a hospital network building an assistant over its own
clinical policy documents, on a network segment with no route to the public internet by
policy, not just by convenience. The chat and embedding models both run locally — an
ollama connection serving a model already approved for that environment, listed in
PRIVATE_UPSTREAM_ALLOWLIST by its Compose service name so the outbound guard accepts
it. Documents are uploaded once, ingested entirely inside the network (parsing, chunking
and embedding are local computation against the local model, with nothing to reach
outside for), and every subsequent question is answered from that store with no request
ever leaving the segment. The one step that touched the outside world was verifying the
container image’s signature before it was ever copied onto that segment in the first
place — a deliberate, one-time crossing rather than an ongoing dependency.
Running more than one replica behind the air gap
Nothing about scaling out changes once a deployment is isolated. Several Ragmux
replicas can sit behind a plain load balancer inside the same segment, sharing rate
limits, budgets, sessions and the document-ingestion queue through one PostgreSQL
database — the same story documented for any multi-replica Ragmux deployment, with no
Redis or other shared cache tier to add. The one setting that matters more here than
elsewhere is SECRET_KEY: every replica has to start with the exact same value, since a
mismatch is a boot failure by design (the gateway seals a canary value into the database
on first start and every replica after that verifies against it), and there is no
external secrets service to fetch it from automatically in a network with no path to
one — it has to be provisioned to every replica through whatever internal mechanism the
environment already uses for secrets.
Where this doesn’t fit
- A network that’s air-gapped except for the LLM calls themselves. If cloud model providers are still reachable and only the gateway’s own control plane needs to stay internal, that’s a much smaller constraint than this page assumes, and a hosted or cloud-adjacent connection type works fine alongside a self-hosted gateway.
- A compliance requirement for a certification Ragmux doesn’t hold. Self-hosting satisfies data residency; it does not produce a SOC2 report or a GDPR attestation on its own, and a program that specifically needs one should evaluate what building that attestation around a self-hosted, uncertified project actually takes.
- Automatic patching. An air-gapped install has no update-check mechanism to lean on; keeping current means an operator deliberately re-running the verify-and-transfer steps above on a schedule they own.
Deploy Ragmux in an air-gapped network
Pull and verify the image on a network-connected host, then transfer it
Pull ragmux/ragmux:<version> from Docker Hub (or the ghcr.io mirror) where internet access exists, verify it with cosign verify against the release workflow's identity, then move the verified image — or better, the manifest digest cosign verify reports — across the air gap with docker save / docker load. Verification itself needs the network; do it before the transfer, not after.
Point every model and embedding connection at a local server
Add connections with provider_type ollama, or custom_openai for vLLM, LM Studio or another OpenAI-compatible server running inside the same isolated network. No cloud provider connection will resolve without outbound access, so every chat and embedding model a project uses has to be reachable from inside the air gap.
Allow those local hosts through the private-upstream guard
A base_url that resolves to a private or loopback address is refused by default. Add the local server's hostname to PRIVATE_UPSTREAM_ALLOWLIST (for example ollama or a Compose service name) before creating the connection, rather than disabling the guard instance-wide with ALLOW_PRIVATE_UPSTREAMS. The allowlist matches hostnames only — an IP literal such as 192.168.1.50 is never matched, so a local server has to be reachable by a name, not just an address, for the allowlist to work.
Set SECRET_KEY explicitly and keep it with your offline backups
Generate it with openssl rand -hex 32 and pass it as an environment variable rather than relying on the generated secret.key fallback, which is documented for local development only. In a network with no path back to a key-management service, SECRET_KEY and your database backup together are the entire recovery story.
Create the first administrator without the setup form
Set ADMIN_USER and ADMIN_PASSWORD before first start so the account exists on boot; the interactive first-run setup form still works, but an unattended install behind an air gap has nobody to click through it.
Put a reverse proxy in front and turn on the audit trail
Terminate TLS at a reverse proxy inside the isolated network, set TRUST_PROXY_HEADERS and SECURE_COOKIES accordingly, and leave AUDIT_RETENTION_DAYS at its default (365 days) or raise it if a compliance review needs a longer window than that.
Frequently asked questions
- Can Ragmux run with no outbound internet access at request time?
- Yes — pair every model and embedding connection with a local server (Ollama, vLLM or another OpenAI-compatible endpoint) reachable inside the same network, and no request ever needs to leave it. The one exception is the initial cosign signature check, which needs network access and should happen before the image crosses the air gap, not after.
- Does Ragmux call home, phone out, or send telemetry anywhere on its own?
- Nothing in Ragmux's documented environment variables describes a telemetry, license-check or update-check call. The configuration reference lists every variable the gateway reads, and none names an analytics or phone-home endpoint — its only outbound calls are to the provider and rerank endpoints an admin explicitly configures.
- How does an air-gapped Ragmux deployment help with GDPR data residency?
- Running Ragmux inside infrastructure you physically control keeps documents, embeddings, request logs and provider credentials on that infrastructure rather than a vendor's. That is the data-residency question GDPR's cross-border transfer rules are actually about, though it is not the same as a GDPR compliance certification, which Ragmux does not carry.
- How does a signed Ragmux image get onto a network with no outbound access at all?
- It's verified with cosign on a connected host first, then carried across the boundary as a file. Pull the image where internet access exists, run cosign verify against the release workflow's identity, then move the verified image — ideally pinned to the digest cosign verify reports — with docker save and docker load or an internal registry mirror, since nothing about running the gateway afterward needs that connection again.
- Do local model servers need special configuration to work behind Ragmux's outbound-connection guard?
- Yes — a base_url that resolves to a private or loopback address is refused by default as an SSRF guard, with an explicit error naming the host and the two ways to allow it. A local Ollama or vLLM hostname has to be added to PRIVATE_UPSTREAM_ALLOWLIST before the connection is created, and the allowlist matches hostnames only — an IP literal like 192.168.1.50 is never matched, so a server reachable only by address still needs a resolvable hostname for this to work.
- Does Ragmux's audit log support an internal compliance review?
- It covers every create, update, delete, login, key rotation and password change with the actor, target and IP address recorded, exportable as NDJSON. It is a record of administrative actions, not a certification, and an auditor should treat it as exactly that.