Railway

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.

Deploy OmniRoute v3.8.49 | AI Gateway Whose Providers and Keys Survive a Redeploy

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-railwayFROM 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

More templates in this category

View Template
Rocky Linux
[Jul'26] Hosted Rocky Linux 9 workspace with SSH and persistent storage. 🚀

codestorm
36
View Template
Foundry Virtual Tabletop
A Self-Hosted & Modern Roleplaying Platform

Lucas
71
View Template
Letta Code Remote
Run a Letta Code agent 24/7. No inbound ports, just deploy.

Letta
51