Deploy Convex [Updated Sep '26]

Convex self-hosted — Backend + Dashboard + Postgres, verified live

Deploy Convex [Updated Sep '26]

Just deployed

Just deployed

/convex/data

Postgres

postgres:17

Just deployed

/var/lib/postgresql/data

Deploy and Host Convex (Self-Hosted) on Railway

Convex is an open-source reactive database built for TypeScript — queries, mutations, and real-time subscriptions without hand-writing a sync layer yourself. This template deploys the self-hosted stack verified live: a full first-boot cycle, admin key generation, and both public endpoints confirmed working, not assumed from a reference config.

About Hosting Convex

Self-hosting Convex means running three pieces together: the Backend (the reactive database and function runtime), the Dashboard (a web UI for logs, data, and functions), and Postgres (the Backend's storage). All three are wired together automatically — networking, volumes, and reference variables already set so the services find each other without manual configuration.

What makes Convex different from a normal database-plus-API setup is that "reactive" isn't marketing language — it's the execution model. A query function isn't just called once and forgotten; the Backend tracks exactly which documents and indexes it read, and if any change, every subscribed client gets pushed the new result automatically. No polling, no per-feature WebSocket wiring, no cache-invalidation logic to get wrong. You write a query as a plain TypeScript function; reactivity is the platform's job.

The one genuinely manual step is the admin key, worth understanding rather than treating as friction. Convex derives it deterministically from two values — INSTANCE_NAME and INSTANCE_SECRET — via its own generate_key binary. Both only exist once the container has started, so the key can't be pre-computed into the template ahead of time, on Railway or anywhere else. The Backend generates it on first boot and prints it to the deploy logs; copy it into CONVEX_SELF_HOSTED_ADMIN_KEY and redeploy once. That's simpler than Convex's own official Railway guide, which asks you to run railway ssh and execute the script by hand — here it just happens on boot.

What Was Verified

Deployed fresh, twice — new project, new Postgres volume, no build cache, reproducing what a first deploy of this template actually looks like rather than testing against an already-warm environment. Both times: all three services reached a clean running state, the Backend's /version healthcheck returned 200, the Dashboard's / returned 200, and the admin key printed to the logs in the correct railway|... format.

The Backend's own boot sequence is visible in its deploy logs, worth knowing what normal looks like: a Postgres connection pool opens, a lease is acquired (guaranteeing only one Backend instance writes at a time, even during a redeploy), then roughly thirty system tables get created — _db, _auth, _scheduled_jobs, _file_storage, and so on — followed by index bootstrapping. Seeing that complete cleanly is the real proof of health, beyond just the healthcheck passing.

One real thing showed up on the very first connection attempt: a transient Database Timeout (Postgres) error, because Postgres was still finishing its own startup when the Backend first tried to connect. The container's own restart policy retried automatically, and the second attempt connected cleanly and finished booting — both ports (3210 for the API, 3211 for HTTP Actions) listening. No intervention needed, and no different from what a real first deploy of this template looks like for anyone using it — Postgres and the Backend starting at roughly the same time is just how the composition works, and the restart policy exists to absorb that timing gap.

Public vs. Private Deployment

Defaults are set for public access — CONVEX_CLOUD_ORIGIN/CONVEX_SITE_ORIGIN point at the PUBLIC_* variables, the Backend has both a public domain (3210) and a TCP proxy (3211), matching what PUBLIC_CONVEX_SITE_ORIGIN needs to resolve. For internal-only, switch both to PRIVATE_* — the Dashboard isn't available in private mode, since it needs a public URL for a browser to load.

The distinction matters beyond "can I reach it": PRIVATE_CONVEX_SITE_ORIGIN uses RAILWAY_PRIVATE_DOMAIN directly on port 3211, reachable only from other services in the same project, with no public exposure. PUBLIC_CONVEX_SITE_ORIGIN has to route through Railway's TCP proxy instead, since the private domain doesn't exist outside Railway's network — so it gets a different, Railway-assigned domain:port pair unique to your deployment.

Configuration

Almost nothing needs your input beyond the one admin-key paste-back. INSTANCE_SECRET and POSTGRES_PASSWORD are auto-generated. INSTANCE_NAME references the Postgres service's own database name automatically. PORT on the Backend (3210) shouldn't be changed — the start command and networking config both assume it. DO_NOT_REQUIRE_SSL is true by default because Railway's own edge already terminates TLS for the public domain; the Backend itself doesn't need to enforce it again on top.

Common Use Cases

  • Real-time apps: chat, live dashboards, collaborative tools that need instant updates pushed to every client, without a WebSocket layer or polling loop to fake it
  • Serverless backends: APIs with automatic scaling and no cold-start tax, since functions run inside the same reactive runtime as your data
  • Full-stack projects: a unified backend for Next.js, React, or any frontend, self-hosted rather than on Convex's managed cloud — useful when data residency is a hard requirement
  • Migrating off managed Convex: the self-hosted Backend runs the same open-source code as the cloud product, so functions and schemas port over largely unchanged

Dependencies for Convex Hosting

Running Convex self-hosted means three services together, plus your own frontend hosted separately (Vercel, Netlify, elsewhere) — this template covers the data and function layer, not the frontend.

Deployment Dependencies

The Backend (ghcr.io/get-convex/convex-backend:latest) and Dashboard (ghcr.io/get-convex/convex-dashboard:latest) are Convex's own official images — nothing custom-built here. Postgres (postgres:17) holds your application data and Convex's own system tables. Docs: docs.convex.dev, self-hosting guide at github.com/get-convex/convex-backend.

Implementation Details

The Backend's start command handles the full first-boot sequence in one place: writing instance credentials, conditionally generating the admin key if still invalid, then handing off to the real backend process — matching Convex's documented flow, just automated instead of requiring railway ssh. The Dashboard is a separate, stateless service pointing at the Backend's Cloud API — it holds no data of its own.

Why Deploy Convex on Railway?

The alternative is Docker Compose on your own VPS: install Docker, wire up Postgres yourself, configure networking and TLS, and still run the same manual admin-key script Convex's own docs describe. Railway replaces the infrastructure setup with three pre-wired services, persistent volumes, and public networking already configured — leaving only the one step inherent to self-hosted Convex on any platform, not specific to Railway.

Compared to Convex's managed cloud: self-hosting trades their free tier and zero-ops experience for full control over your data — worth it if you need data on infrastructure you control, or you're already consolidating other services on Railway.

How Convex Compares

Against Firebase: Firebase's reactivity is document/collection-scoped — you subscribe to a path. Convex tracks arbitrary query logic, including joins across tables, so a query combining data from several places stays live without fanning out multiple listeners yourself.

Against Supabase: Supabase layers real-time onto Postgres logical replication under a fairly standard REST/SQL model. Convex's functions and reactivity are the primary interface — the sync layer is intrinsic to how a query executes, not a separate feature bolted on.

Frequently Asked Questions

Why do I need to manually set the admin key after deploying?

It's cryptographically derived from values that only exist once the container starts, so it can't be pre-baked into the template. The Backend generates and logs it automatically — you copy it into a variable once.

What's the difference between the Backend's two ports?

Port 3210 is the Cloud API (queries, mutations, subscriptions). Port 3211 serves HTTP Actions — functions exposed over plain HTTP. Both need public networking for a public deployment.

Can I run this without exposing it publicly?

Yes — switch the origin variables to their PRIVATE_* equivalents. The Dashboard won't be reachable that way, since it needs a public URL to load in a browser.

Why Postgres instead of the default SQLite?

SQLite works for local dev, but doesn't hold up well across redeploys and volume semantics on Railway. Postgres is the production-ready option Convex documents for this case.

Does the Dashboard store any data itself?

No — it reads/writes the Backend's Cloud API using your admin key. Delete and redeploy it entirely and you'd lose nothing.

What happens to my data if I redeploy the Backend?

Nothing — state lives in the Postgres volume and the Backend's own /convex/data volume (local file/search storage). A redeploy reconnects to the same Postgres instance and picks up where it left off.

Where can I download Convex?

Source is on GitHub at github.com/get-convex/convex-backend. Use this template to deploy the self-hosted stack correctly configured in one click.

Source for this template's docs: https://github.com/shruti060701/convex-railway


Template Content

More templates in this category

View Template
Garage S3 Storage
Ultra-light S3 server: fast, open-source, plug-and-play.

PROJETOS
8
View Template
Redis
Self Host Latest Redis with Railway

8
View Template
EasyImg
Simple self-hostable Nuxt.js personal image hosting system.

Muhammad Bilal
0