--- name: railway-cli description: "Manage Railway projects/services from the VPS with the railway CLI plus the Railway GraphQL API. Covers the RAILWAY_API_TOKEN vs RAILWAY_TOKEN gotcha, account vs project tokens, listing services across workspaces, finding the source-of-truth deploy for a project with multiple jungle-* style services, and pulling the underlying source via gh from the linked GitHub repo." version: 1.0.0 tags: [railway, cli, deployment, graphql, exxir, jungle] metadata: hermes: tags: [railway, cli, deployment, graphql, exxir, jungle] related_skills: [vps-app-deployment, github-repo-management] --- # Railway CLI + API Operational patterns for inspecting and managing Alex's (and exxir's) Railway-hosted projects from the VPS. ## When to Use - Listing/inspecting Railway projects, services, deployments, custom domains - Finding which of N similarly-named services is the actual live one - Linking an external Railway-hosted source (e.g. `Exxir/jungle-website`) so Hermes can read/learn from it - Anything Railway-related — do NOT confuse this with `vps-app-deployment` which is self-hosted at apps.poofc.com ## Install + Auth Install (Node global): ```bash 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: ```bash RAILWAY_API_TOKEN=xxx railway whoami # → Logged in as RAILWAY_API_TOKEN=xxx railway list # → workspaces + projects ``` ### Persisting the token Save to `~/.hermes/.env` so all subsequent shells pick it up: ```bash 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: ```bash 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: ```bash 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`: ```bash 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: ```bash 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 - **`RAILWAY_TOKEN` works for `railway login --browserless`/PAT flow but NOT for account tokens** — always use `RAILWAY_API_TOKEN` for headless/account use cases. - **"No workspace" when creating a token is correct for cross-workspace access** — choosing a specific workspace scopes the token to just that workspace's projects. - **`railway list` is just project names** — service/deployment detail requires GraphQL or `railway status --service NAME`. - **`Service.source` does not exist in GraphQL** — drill into `serviceInstances.edges.node.source`. Same for `domains` and `latestDeployment`. - **Latest deploy ≠ source of truth** — if it's `FAILED`, the previous `SUCCESS` deploy is still serving production. Check both `status` and `createdAt`. - **A service with `source: { repo: null, image: null }` is not GitHub-linked** — it was deployed via Caddyfile/Dockerfile drop or template. You can still inspect it via Railway but you won't find the code on GitHub for that service. - **`railway service NAME` requires the display name with spaces** — `Sync: Jungle` not `sync-jungle`. Quote it or escape spaces. - **Don't put the token in tool output** — paste from `~/.hermes/.env` or pass inline; never echo it back to the user in chat. ## Verification Checklist - [ ] `railway whoami` returns the expected email (firemountain@gmail.com for Alex's account token) - [ ] `railway list` shows expected workspaces (exxir's Projects + My Projects for the full-access token) - [ ] For multi-service projects, GraphQL query confirms which service has the most recent SUCCESS deploy - [ ] Custom domain (if any) resolves to that service via `curl -sI https://DOMAIN/` - [ ] If pulling source, `gh repo view OWNER/REPO` confirms collaborator access before clone