Deploy to Railway
Ship Dash to Railway with JWT verification.
Dash ships with three Railway scripts. Production also enables JWT verification, so the deploy needs one extra step: paste a public key from AgentOS into your env.
Prerequisites
- A Dash repo set up locally (setup)
- A Railway account
- The Railway CLI installed and authenticated (
railway login)
Step 1: Provision the project
Use .env.production to keep production credentials separate from local dev:
1cp example.env .env.productionOpen .env.production and set OPENAI_API_KEY. Leave JWT_VERIFICATION_KEY empty for now. You'll add it in Step 2.
1./scripts/railway_up.shThis script:
- Creates a Railway project called
dash. - Adds a
pgvectorservice with a persistent volume at/var/lib/postgresql/data. - Creates the
dashapplication service and forwards the env vars in.env.production. - Deploys the app and assigns a public domain.
Copy the Railway domain from the output. The app will crash-loop until Step 2 lands. That's expected. Production rejects all requests until a JWT_VERIFICATION_KEY is set.
Step 2: Get a JWT key from AgentOS
The key comes from AgentOS:
- Open os.kern.ndx.rocks and log in.
- Click Add OS → Live, paste your Railway URL, click Connect.
- Go to Settings and click Generate key pair.
- Copy the public key.
- Paste it into
.env.production, wrapped in single quotes:
1JWT_VERIFICATION_KEY='-----BEGIN PUBLIC KEY-----2MIIBIjANBgkq...3-----END PUBLIC KEY-----'Single quotes preserve the multiline PEM block.
Step 3: Push env and redeploy
1./scripts/railway_env.sh2./scripts/railway_redeploy.shrailway_env.sh reads .env.production and pushes every variable to the Dash service. It handles multiline values like PEM keys correctly. Safe to run repeatedly.
railway_redeploy.sh triggers a fresh build of the Dash service.
Step 4: Load data into the production database
The pgvector service is only reachable from inside Railway's network (pgvector.railway.internal). Run the data scripts via SSH:
1railway ssh --service dash2# Inside the container:3python scripts/generate_data.py4python scripts/load_knowledge.py5exitVerify
1curl https://your-dash.up.railway.app/health2# {"status":"ok"}Then talk to Dash through the AgentOS UI you connected in Step 2.
Update Slack to point at production
If you set up Slack against an ngrok URL, swap it for your Railway URL:
- Open your Slack app at api.slack.com/apps.
- Go to Event Subscriptions.
- Update the Request URL to
https://your-dash.up.railway.app/slack/events. - Wait for the green Verified check, then Save Changes.
You can stop ngrok now. Slack delivers events to Railway from now on.
Operations
| Task | Command |
|---|---|
| Tail logs | railway logs --service dash |
| Open the dashboard | railway open |
| Run a command in the container | railway ssh --service dash |
Sync env after a .env.production change | ./scripts/railway_env.sh |
| Redeploy after a code change | ./scripts/railway_redeploy.sh |
How RBAC works
Dash holds a structural boundary between company data (public, read-only) and agent-managed data (dash, read/write). RBAC adds a complementary boundary at the API: every query is scoped to a user_id so multi-user deployments stay isolated.
Local development sets RUNTIME_ENV=dev (via Docker Compose) and skips RBAC so you can iterate. Production defaults to RUNTIME_ENV=prd and enforces it.
See AgentOS Security and RBAC.