| name | deploy-railway |
|---|---|
| description | Deploy the Hyperindex frontend and backend to Railway. Use this skill when the user asks to deploy, redeploy, or update the production services on Railway. |
Hyperindex is a monorepo with two Railway services:
| Service | Source | Dockerfile | Railway Name |
|---|---|---|---|
| Backend (Go) | repo root / |
Dockerfile |
backend |
| Frontend (Next.js) | client/ |
client/Dockerfile |
frontend |
| Service | Domain |
|---|---|
| Backend | https://api.hi.gainforest.app |
| Frontend | https://hi.gainforest.app |
Legacy domains (still active): backend-production-95a22.up.railway.app, frontend-production-dcce.up.railway.app
- Railway CLI v4+ installed and logged in (
railway whoami) - Linked to project:
railway statusshould show projecthyperindex - On the correct git branch (typically
tap-feature)
The backend deploys from the repo root using the root Dockerfile:
railway up -s backend -dThis uploads the entire repo, builds the Go binary in Docker, and deploys it. Takes ~3-5 minutes.
curl -s https://api.hi.gainforest.app/
# Should return: {"name":"Hyperindex","version":"0.1.0-dev",...}CRITICAL: The frontend MUST use --path-as-root to avoid Railway picking up the root Go Dockerfile:
railway up --path-as-root client/ -s frontend -dThis makes client/ the archive root so Railway only sees client/Dockerfile (the Next.js build). Takes ~3-5 minutes.
Without it, railway up uploads the entire monorepo and Railway finds the root Dockerfile (Go backend) instead of client/Dockerfile (Next.js frontend). This causes the frontend service to run the Go binary instead of the Next.js app.
curl -s -o /dev/null -w "%{http_code}" https://hi.gainforest.app/
# Should return: 200
# Verify it's actually Next.js (not the Go server):
curl -s https://hi.gainforest.app/ | grep -o '<title>[^<]*</title>'
# Should return: <title>Hyperindex</title># Backend (from repo root)
railway up -s backend -d
# Frontend (with path-as-root)
railway up --path-as-root client/ -s frontend -d| Variable | Value |
|---|---|
HOST |
0.0.0.0 |
PORT |
8080 |
DATABASE_URL |
sqlite:/app/data/hypergoat.db |
EXTERNAL_BASE_URL |
https://api.hi.gainforest.app |
TRUST_PROXY_HEADERS |
true |
ADMIN_DIDS |
did:plc:qc42fmqqlsmdq7jiypiiigww (daviddao.org) |
OAUTH_LOOPBACK_MODE |
true |
SECRET_KEY_BASE |
(set on Railway, do not change) |
| Variable | Value |
|---|---|
PORT |
3000 |
NEXT_PUBLIC_CLIENT_URL |
https://hi.gainforest.app |
NEXT_PUBLIC_API_URL |
https://api.hi.gainforest.app |
HYPERINDEX_URL |
https://api.hi.gainforest.app |
COOKIE_SECRET |
(set on Railway, do not change) |
ATPROTO_JWK_PRIVATE |
(ES256 JWK, set on Railway, do not change) |
Note: NEXT_PUBLIC_API_URL is a build-time variable (inlined by Next.js during npm run build). The client/Dockerfile declares ARG NEXT_PUBLIC_API_URL so Railway passes it during Docker build.
You forgot --path-as-root client/. Redeploy with:
railway up --path-as-root client/ -s frontend -dSSL certificate is still provisioning. Wait 5-15 minutes after adding DNS records.
GraphiQL is served directly by the backend. The frontend has a /graphiql server-side redirect route that redirects to https://api.hi.gainforest.app/graphiql.
Check that ATPROTO_JWK_PRIVATE and NEXT_PUBLIC_CLIENT_URL are set on the frontend service. Generate a new JWK with:
node scripts/generate-jwk.js # (in hyperscan repo, or client/scripts/ if copied)Ensure TRUST_PROXY_HEADERS=true is set on the backend. Without it, the backend ignores the X-User-DID header from the Next.js proxy.
# Set a variable on a service
railway variables set 'KEY=value' -s backend
railway variables set 'KEY=value' -s frontend
# View all variables for a service
railway variables -s backend
railway variables -s frontendAfter changing env vars, redeploy the affected service.