Deploy OmniRoute v3.8.49 | AI Gateway Whose Providers and Keys Survive a Redeploy
[v3.8.49] AI gateway whose providers and keys survive a redeploy.
omniroute
Just deployed
/app/data
Deploy and Host OmniRoute AI Gateway on Railway
OmniRoute is a self-hosted AI gateway: one OpenAI-compatible /v1 endpoint in front of many
LLM providers, with a dashboard for provider credentials, generated API keys, model aliases,
routing rules and automatic fallback. Point Claude Code, Codex, Cursor, Cline, OpenWebUI or any
OpenAI-compatible SDK at a single base URL and change providers behind it without touching the
client.
About Hosting OmniRoute AI Gateway
This template runs ghcr.io/bon5co/omniroute-railway, a thin wrapper over the official upstream
image pinned at 3.8.49. No fork and no patches — only the configuration a Railway deployment
needs, plus three corrections that are wrong by default on this platform. One service and one
volume; nothing else to pay for.
Your providers, endpoints and API keys survive a redeploy. This is the whole reason the
template exists. OmniRoute keeps every piece of its state in one directory — storage.sqlite
holds the provider connections, generated API keys and routing rules, and server.env holds the
auto-generated JWT_SECRET and STORAGE_ENCRYPTION_KEY that those stored credentials are
encrypted with. The upstream image is explicit about where that directory is:
# Data directory inside Docker — must match the volume mount in docker-compose.yml
ENV DATA_DIR=/app/data
A volume mounted anywhere else persists nothing at all, and it does so quietly — the service
deploys, reports healthy, and serves traffic. Measured on 3.8.49 with the volume on /data and
the container then recreated the way a redeploy does: a brand-new storage.sqlite, a brand-new
JWT_SECRET, a brand-new STORAGE_ENCRYPTION_KEY, and the volume still empty. Every provider
you connected is gone, and the key that could have decrypted the old credentials went with them.
Mounting on the correct path is not enough either. Railway mounts volumes owned by uid 0 while
this image runs as node (uid 1000), so the app cannot write into its own data directory — and
it does not crash. It reports healthy, logs [DB] SQLite database ready, and fails every
write underneath ([bootstrap] Could not persist secrets … EACCES, [sqljsAdapter] save failed … EACCES). This template mounts the volume at /app/data and repairs its ownership before the
app's privilege drop, so the writes actually land. Verified on a live Railway deployment: the
first boot applied 123 database migrations, and after a redeploy it applied zero — the
database was read back off the volume rather than rebuilt.
The dashboard is password-protected from the first boot. With no password configured,
OmniRoute leaves POST /api/settings/require-login outside its auth check so a first-run user
can finish onboarding — and isAuthRequired() tests requireLogin === false before anything
else. On a public URL those two compose. Measured against 3.8.49 with no initial password, every
call anonymous and from off-box:
GET /api/keys -> 401 (looks secure)
POST /api/settings/require-login -> 200 {"success":true}
GET /api/keys -> 200 auth is now off, for everyone, permanently
Whoever finds the URL can then read and mint API keys and add providers against your credits.
This template generates a per-deploy password as ${{secret(24)}}, which Railway shows in the
service's variables panel. The same three calls against this template's live deployment return
401 / 401 / 401.
The heap is sized from your container, not from the image. Upstream bakes
--max-old-space-size=1024 in, with its own note that 1024 MB "can be tight for large
fusion-combo panels (many models fanned out in parallel, each response buffered in full)". On
Railway the container size is a deploy-time choice, so the entrypoint reads
/sys/fs/cgroup/memory.max and takes 70% of it. Measured heap_size_limit: 1120 MB stock vs
5830 MB in an 8 GB container, and it scales down correctly too (1481 MB in a 2 GB one). A
OMNIROUTE_MEMORY_MB you set yourself still overrides it.
The image tag is pinned rather than tracking latest, because OmniRoute applies forward-only
SQLite migrations on boot — 123 of them at this version — and an unpinned image upgrades your
database on a redeploy you did not ask for.
There is deliberately no Redis service. REDIS_URL is read only by OmniRoute's rate limiter,
which falls back to in-memory limiting when it is unset (upstream issue #2357, "Redis is
optional"), and a template deploy is single-replica, so a shared rate-limit backend buys nothing
a process-local one does not already give. CLOUD_URL is left unset for a related reason: the
cloud-sync path POSTs a bundle containing providers and apiKeys to
${CLOUD_URL}/sync/, so it should point somewhere you chose, not somewhere a
template chose for you.
Common Use Cases
- One OpenAI-compatible endpoint in front of OpenAI, Anthropic, Gemini, OpenRouter and local models
- Keeping provider API keys on infrastructure you own instead of pasting them into every tool
- Giving coding agents (Claude Code, Codex, Cursor, Cline) a base URL you can re-point without reconfiguring them
- Per-key quotas, rate limits and spend visibility across a team
- Automatic fallback when a provider rate-limits or errors
Why Deploy OmniRoute on Railway
Railway gives the gateway a public HTTPS URL, a persistent volume and a private network without
any of it being configured by hand. Deploy this template, open the URL, log in with the
generated password from the variables panel, add your providers, and point your tools at
https://your-domain/v1.
Because it is a gateway rather than a batch job, it is always on — which is exactly the shape Railway's usage billing suits, and exactly the shape that makes losing its database on every redeploy expensive rather than annoying.
Dependencies for OmniRoute AI Gateway
- API keys for whichever LLM providers you want to route to (added from the dashboard after deploy)
Deployment Dependencies
- Wrapper image:
ghcr.io/bon5co/omniroute-railway—FROM diegosouzapw/omniroute:3.8.49 - Upstream project: OmniRoute
- A Railway volume mounted at
/app/data INITIAL_PASSWORD, generated per deploy as${{secret(24)}}- Public HTTP networking with the healthcheck at
/api/monitoring/health
Template Content
