Skip to content

Engine on Railway

Railway runs the engine image as a service built from a Docker image. One service can publish both engine ports, each on its own domain. New accounts get trial credits, and after that a service is billed by usage. A deployed service keeps running rather than sleeping, so check Railway's current pricing before you leave it unattended.

  • A Railway account and a project.
  • A database. A Railway Postgres service in the same project works, or Supabase or Neon.
  1. Add a service to the project from the Docker image ghcr.io/lyeve-labs/lyeve-core:latest. Pin a release tag in production. See Docker images.
  2. In the service's networking settings, give one public domain to port 3001 (the Admin API) and one to port 3002 (the Content API).
  3. Add a volume mounted at /var/lib/lyeve, so the signing key survives a redeploy. The engine runs as uid 65532, and Railway documents that an image running as a non-root user cannot write to an attached volume unless the service sets RAILWAY_RUN_UID=0, which runs the container as root. Set it, or the engine cannot write the key. In production the engine refuses to start when it cannot write the key.

Without the volume, every redeploy creates a new key and every token issued before it stops working.

Set these in the service's variables:

DATABASE_URL=postgres://<user>:<password>@<host>:5432/<dbname>?sslmode=require
JWT_SECRET=<openssl rand -hex 32>
ENCRYPTION_KEY=<a different openssl rand -hex 32>
LYEVE_AUDIT_HMAC_KEY=<a third openssl rand -hex 32>
RATE_LIMIT_RPS=100
SECURE_COOKIE=true
CORS_ORIGINS=https://your-app.example.com
RAILWAY_RUN_UID=0

Production refuses to start without these values. Configuration lists every check.

For a Railway Postgres service, use the internal connection string Railway generates, which stays on Railway's network. Use the public one only when something outside Railway reaches the database. Production refuses the database user postgres, so create a dedicated user for the engine. Supported databases lists the URL forms.

Set the service's health check path to /readyz. Both listeners serve /healthz, /readyz and /startup without authentication. Do not use /api/admin/health or /api/v1/health: they need authentication, so a check gets 401 and marks a healthy service down. Health endpoints says what each one checks.

Terminal window
curl https://<content-api-domain>/.well-known/jwks.json
curl https://<admin-api-domain>/api/admin/setup

The JWKS document is on the domain for port 3002, and the setup route on the domain for port 3001. A new install answers {"setup_required":true,"token_source":"log"}. The setup token is the one-time setup_token in the service's log, or LYEVE_SETUP_TOKEN when you set it. The quickstart continues from step 2.