railway-cli

/home/avalon/.hermes/skills/devops/railway-cli/SKILL.md · raw

Railway CLI + API

Operational patterns for inspecting and managing Alex's (and exxir's) Railway-hosted projects from the VPS.

When to Use

Install + Auth

Install (Node global):

npm i -g @railway/cli
which railway && railway --version    # expect: /home/avalon/.npm-global/bin/railway, 4.59+

CRITICAL: env var is RAILWAY_API_TOKEN, NOT RAILWAY_TOKEN

This is the #1 footgun. The CLI accepts BOTH names but they mean different things:

Env var Token type What it works with
RAILWAY_TOKEN Project token (created inside one project's Settings → Tokens) Scoped to one project, fails for multi-project listing
RAILWAY_API_TOKEN Account token (created from user settings → Tokens, "no workspace" = all workspaces) Works for whoami, list, cross-workspace ops

Symptom of mixing them up: railway whoami returns Unauthorized. Please check that your RAILWAY_TOKEN is valid... even though the token works fine — because the token is an account token and the CLI looked for a project token under RAILWAY_TOKEN.

Fix: export as RAILWAY_API_TOKEN for account tokens. Verify with:

RAILWAY_API_TOKEN=xxx railway whoami      # → Logged in as <email>
RAILWAY_API_TOKEN=xxx railway list        # → workspaces + projects

Persisting the token

Save to ~/.hermes/.env so all subsequent shells pick it up:

echo "RAILWAY_API_TOKEN=YOUR_TOKEN_HERE" >> ~/.hermes/.env
chmod 600 ~/.hermes/.env

If railway whoami still fails after that, the wrapping shell may not have sourced the env — pass it inline once: RAILWAY_API_TOKEN=$(grep RAILWAY_API_TOKEN ~/.hermes/.env | cut -d= -f2) railway whoami.

Linking a project for local CLI use

railway link is interactive but can be driven with flags:

mkdir -p /tmp/railway-PROJECT && cd /tmp/railway-PROJECT
railway link --project "Exxir App"        # picks workspace + project + env (production)
railway status                            # shows linked project + ID + env ID

A project with multiple services requires --service NAME for service-scoped ops:

railway service Sync\\ QB                  # NOTE: railway uses display names with spaces
railway logs --service "jungle-website"

Listing services + finding the source-of-truth deploy

railway list only shows project names. For service-level detail (URLs, last deploy, status, linked repo) use the GraphQL API directly — it's faster and works without railway link:

RAILWAY_API_TOKEN=xxx
PROJECT_ID=$(railway status --json 2>/dev/null | jq -r .projectId)  # or hardcode from `railway status`

curl -s -X POST https://backboard.railway.com/graphql/v2 \
  -H "Authorization: Bearer $RAILWAY_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"query\":\"query { project(id: \\\"$PROJECT_ID\\\") { services { edges { node { name serviceInstances { edges { node { source { repo image } domains { serviceDomains { domain } customDomains { domain } } latestDeployment { status createdAt staticUrl } } } } } } } } }\"}"

Key field gotchas in the schema: - Service itself does NOT have a source field — query serviceInstances.edges.node.source { repo image } instead - Same for domains and latestDeployment — they live under serviceInstances - A repo: null, image: null source means the service was deployed via Caddyfile/Dockerfile drop or empty template, NOT GitHub-linked

Use this query to disambiguate "which one is live" when you see e.g. jungle-website, jungle-website-booking-preview, Sync: Jungle in the same project: - Sort by latestDeployment.createdAt desc - A FAILED deployment is NOT the source of truth even if it's the most recent attempt - Check customDomains for the canonical production hostname (e.g. junglestudio.com) vs the railway preview URL

Pulling source from a Railway-linked GitHub repo

When source.repo is set (e.g. Exxir/jungle-website), clone via gh — Alex has collaborator access on exxir repos:

cd /home/avalon
gh repo view Exxir/jungle-website --json defaultBranchRef,pushedAt,description,url
git clone https://github.com/Exxir/jungle-website.git jungle-website-ref

If the clone partially fails with No space left on device, see the disk-cleanup pitfall in vps-app-deployment. After freeing space, finish the checkout with git restore --source=HEAD :/ from inside the clone.

Common Pitfalls

Verification Checklist