use-railway
Operate Railway infrastructure: create projects, provision services and databases, manage object storage buckets, deploy code, configure environments and variables, manage domains, troubleshoot failures, check status and metrics, and query Railway docs. Use this skill whenever the user mentions Railway, deployments, services, environments, buckets, object storage, build failures, or infrastructure operations, even if they don't say "Railway" explicitly.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install railwayapp-railway-skills-use-railway
Repository
Skill path: plugins/railway/skills/use-railway
Operate Railway infrastructure: create projects, provision services and databases, manage object storage buckets, deploy code, configure environments and variables, manage domains, troubleshoot failures, check status and metrics, and query Railway docs. Use this skill whenever the user mentions Railway, deployments, services, environments, buckets, object storage, build failures, or infrastructure operations, even if they don't say "Railway" explicitly.
Open repositoryBest for
Primary workflow: Write Technical Docs.
Technical facets: Full Stack, DevOps, Tech Writer.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: railwayapp.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install use-railway into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/railwayapp/railway-skills before adding use-railway to shared team environments
- Use use-railway for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: use-railway
description: >
Operate Railway infrastructure: create projects, provision services and
databases, manage object storage buckets, deploy code, configure environments
and variables, manage domains, troubleshoot failures, check status and metrics,
and query Railway docs. Use this skill whenever the user mentions Railway,
deployments, services, environments, buckets, object storage, build failures,
or infrastructure operations, even if they don't say "Railway" explicitly.
allowed-tools: Bash(railway:*), Bash(which:*), Bash(command:*), Bash(npm:*), Bash(npx:*), Bash(curl:*)
---
# Use Railway
## Railway resource model
Railway organizes infrastructure in a hierarchy:
- **Workspace** is the billing and team scope. A user belongs to one or more workspaces.
- **Project** is a collection of services under one workspace. It maps to one deployable unit of work.
- **Environment** is an isolated configuration plane inside a project (for example, `production`, `staging`). Each environment has its own variables, config, and deployment history.
- **Service** is a single deployable unit inside a project. It can be an app from a repo, a Docker image, or a managed database.
- **Bucket** is an S3-compatible object storage resource inside a project. Buckets are created at the project level and deployed to environments. Each bucket has credentials (endpoint, access key, secret key) for S3-compatible access.
- **Deployment** is a point-in-time release of a service in an environment. It has build logs, runtime logs, and a status lifecycle.
Most CLI commands operate on the linked project/environment/service context. Use `railway status --json` to see the context, and `--project`, `--environment`, `--service` flags to override.
## Preflight
Before any mutation, verify context:
```bash
command -v railway # CLI installed
railway whoami --json # authenticated
railway --version # check CLI version
railway status --json # linked project/environment/service
```
If the CLI is missing, guide the user to install it.
```bash
bash <(curl -fsSL cli.new) # Shell script (macOS, Linux, Windows via WSL)
brew install railway # Homebrew (macOS)
npm i -g @railway/cli # npm (macOS, Linux, Windows). Requires Node.js version 16 or higher.
```
If not authenticated, run `railway login`. If not linked, run `railway link --project <id-or-name>`.
If a command is not recognized (for example, `railway environment edit`), the CLI may be outdated. Upgrade with:
```bash
railway upgrade
```
## Common quick operations
These are frequent enough to handle without loading a reference:
```bash
railway status --json # current context
railway whoami --json # auth and workspace info
railway project list --json # list projects
railway service status --all --json # all services in current context
railway variable list --service <svc> --json # list variables
railway variable set KEY=value --service <svc> # set a variable
railway logs --service <svc> --lines 200 --json # recent logs
railway up --detach -m "<summary>" # deploy current directory
railway bucket list --json # list buckets in current environment
railway bucket info --bucket <name> --json # bucket storage and object count
railway bucket credentials --bucket <name> --json # S3-compatible credentials
```
## Routing
For anything beyond quick operations, load the reference that matches the user's intent. Load only what you need, one reference is usually enough, two at most.
| Intent | Reference | Use for |
|---|---|---|
| Create or connect resources | [setup.md](references/setup.md) | Projects, services, databases, buckets, templates, workspaces |
| Ship code or manage releases | [deploy.md](references/deploy.md) | Deploy, redeploy, restart, build config, monorepo, Dockerfile |
| Change configuration | [configure.md](references/configure.md) | Environments, variables, config patches, domains, networking |
| Check health or debug failures | [operate.md](references/operate.md) | Status, logs, metrics, build/runtime triage, recovery |
| Request from API, docs, or community | [request.md](references/request.md) | Railway GraphQL API queries/mutations, metrics queries, Central Station, official docs |
If the request spans two areas (for example, "deploy and then check if it's healthy"), load both references and compose one response.
## Execution rules
1. Prefer Railway CLI. Fall back to `scripts/railway-api.sh` for operations the CLI doesn't expose.
2. Use `--json` output where available for reliable parsing.
3. Resolve context before mutation. Know which project, environment, and service you're acting on.
4. For destructive actions (delete service, remove deployment, drop database), confirm intent and state impact before executing.
5. After mutations, verify the result with a read-back command.
## Composition patterns
Multi-step workflows follow natural chains:
- **Add object storage**: setup (create bucket), setup (get credentials), configure (set S3 variables on app service)
- **First deploy**: setup (create project + service), configure (set variables and source), deploy, operate (verify healthy)
- **Fix a failure**: operate (triage logs), configure (fix config/variables), deploy (redeploy), operate (verify recovery)
- **Add a domain**: configure (add domain + set port), operate (verify DNS and service health)
- **Docs to action**: request (fetch docs answer), route to the relevant operational reference
When composing, return one unified response covering all steps. Don't ask the user to invoke each step separately.
## Setup decision flow
When the user wants to create or deploy something, determine the right action from current context:
1. Run `railway status --json` in the current directory.
2. **If linked**: add a service to the existing project (`railway add --service <name>`). Do not create a new project unless the user explicitly says "new project" or "separate project".
3. **If not linked**: check the parent directory (`cd .. && railway status --json`).
- **Parent linked**: this is likely a monorepo sub-app. Add a service and set `rootDirectory` to the sub-app path.
- **Parent not linked**: run `railway list --json` and look for a project matching the directory name.
- **Match found**: link to it (`railway link --project <name>`).
- **No match**: create a new project (`railway init --name <name>`).
4. When multiple workspaces exist, match by name from `railway whoami --json`.
**Naming heuristic**: app names like "flappy-bird" or "my-api" are service names, not project names. Use the directory or repo name for the project.
## Response format
For all operational responses, return:
1. What was done (action and scope).
2. The result (IDs, status, key output).
3. What to do next (or confirmation that the task is complete).
Keep output concise. Include command evidence only when it helps the user understand what happened.
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/setup.md
```markdown
# Setup
Create, link, and organize Railway projects, services, databases, and workspaces.
## Projects
### List and discover
```bash
railway project list --json # projects in current workspace
railway list --json # all projects across workspaces with service metadata
railway whoami --json # current user, workspace memberships
```
### Link to an existing project
Linking sets the working context for all subsequent CLI commands in this directory.
```bash
railway link --project <project-id-or-name>
railway status --json # confirm linked context
```
Without `--project`, `railway link` runs interactively. For scripted or CI use, always pass explicit flags.
### Link to a specific service
Switch the linked service within an already-linked project:
```bash
railway service link # interactive service picker
railway service link <name> # link directly by name
```
### Create a new project
```bash
railway init --name <project-name>
railway init --name <project-name> --workspace <workspace-id-or-name>
```
`railway init` both creates and links in one step. In CI or multi-workspace setups, pass `--workspace` explicitly to avoid ambiguity.
### Update project settings
Settings like project name, PR deploys, and visibility aren't exposed through the CLI. Use the GraphQL API helper (see [request.md](request.md)):
```bash
scripts/railway-api.sh \
'mutation updateProject($id: String!, $input: ProjectUpdateInput!) {
projectUpdate(id: $id, input: $input) { id name isPublic prDeploys }
}' \
'{"id":"<project-id>","input":{"name":"new-name","prDeploys":true}}'
```
## Services
### Create a service
```bash
railway add --service <service-name> # empty service
railway add --database postgres # managed database (postgres, redis, mysql, mongodb)
railway add # interactive, prompts for type
```
Before adding a database, check for existing database services to avoid duplicates. Run `railway environment config --json` and inspect `source.image` for each service:
| Image pattern | Database |
|---|---|
| `ghcr.io/railway/postgres*` or `postgres:*` | Postgres |
| `ghcr.io/railway/redis*` or `redis:*` | Redis |
| `ghcr.io/railway/mysql*` or `mysql:*` | MySQL |
| `ghcr.io/railway/mongo*` or `mongo:*` | MongoDB |
If a matching database already exists, skip creation and wire the existing service's variables to the app.
Empty services have no source until you configure one. This is the right pattern when you need to set source repo, branch, or build config before the first deploy.
### Connect a database to a service
After `railway add --database <type>`, the database creates connection variables automatically. Wire them to your app service using variable references:
| Database | Connection variable |
|---|---|
| Postgres | `${{Postgres.DATABASE_URL}}` |
| Redis | `${{Redis.REDIS_URL}}` |
| MySQL | `${{MySQL.MYSQL_URL}}` |
| MongoDB | `${{MongoDB.MONGO_URL}}` |
```bash
railway variable set DATABASE_URL='${{Postgres.DATABASE_URL}}' --service <app-service>
```
Service names in variable references are case-sensitive and must match exactly. For full wiring details including public/private networking decisions, see [configure.md](configure.md).
When creating new service instances via JSON config patches, include `isCreated: true` in the service block to mark it as a new service.
### Deploy from a template
Templates provision pre-configured services with sensible defaults, faster than creating an empty service and configuring it manually:
```bash
railway deploy --template <template-code>
```
Common template codes: `postgres`, `redis`, `mysql`, `mongodb`, `minio`, `umami`. For the full list, search via the GraphQL API (see [request.md](request.md)).
Template deployments typically create:
- A service with pre-configured image or source
- Environment variables (connection strings, secrets)
- A volume for persistent data (databases)
- A TCP proxy for external access (where applicable)
### Bootstrap source for an empty service
After creating an empty service, wire it to a repo:
```bash
railway environment edit --service-config <service> source.repo <repo-url>
railway environment edit --service-config <service> source.branch <branch>
```
### Deploy a Docker image
When you have a built image (for example, from a private registry or Docker Hub), skip source builds entirely:
```bash
railway environment edit --service-config <service> source.image <image:tag>
```
This sets the service to pull from a container registry instead of building from source.
## Buckets
Buckets are S3-compatible object storage. They are created at the project level and deployed to environments via config patches.
### List buckets
```bash
railway bucket list --json # buckets in current environment
railway bucket list --environment production --json # buckets in a specific environment
```
### Create a bucket
```bash
railway bucket create my-bucket --region sjc # create with name and region
railway bucket create --region iad --json # auto-named, JSON output
```
Available regions:
| Code | Location |
|---|---|
| `sjc` | US West (California) |
| `iad` | US East (Virginia) |
| `ams` | EU West (Amsterdam) |
| `sin` | Asia Pacific (Singapore) |
Without `--region`, the CLI prompts interactively. For scripted use, always pass `--region`.
### Delete a bucket
Deletion is permanent and destroys all objects in the bucket.
```bash
railway bucket delete --bucket my-bucket --yes # non-interactive
railway bucket delete --bucket my-bucket --yes --2fa-code 123456 # with 2FA
```
### Rename a bucket
```bash
railway bucket rename --bucket my-bucket --name new-name --json
```
### Bucket info
Check storage usage and object count:
```bash
railway bucket info --bucket my-bucket --json
```
Returns storage size (bytes), object count, region, and environment.
### Bucket credentials
Get S3-compatible credentials for connecting your app to a bucket:
```bash
railway bucket credentials --bucket my-bucket --json
```
Returns: `endpoint`, `accessKeyId`, `secretAccessKey`, `bucketName`, `region`, `urlStyle`.
Without `--json`, output uses `AWS_*=value` lines suitable for `eval $(railway bucket credentials)` or piping into `.env` files.
To reset credentials (invalidates existing ones):
```bash
railway bucket credentials --bucket my-bucket --reset --yes
railway bucket credentials --bucket my-bucket --reset --yes --2fa-code 123456 # with 2FA
```
### Connect a bucket to a service
After creating a bucket, wire the S3 credentials to your app service as environment variables:
```bash
# Get credentials
railway bucket credentials --bucket my-bucket --json
# Set them on your app service
railway variable set \
AWS_ENDPOINT_URL=<endpoint> \
AWS_ACCESS_KEY_ID=<access-key> \
AWS_SECRET_ACCESS_KEY=<secret-key> \
AWS_S3_BUCKET_NAME=<bucket-name> \
AWS_DEFAULT_REGION=<region> \
--service <app-service>
```
All subcommands support `--bucket (-b)` and `--environment (-e)` as global flags to skip interactive prompts.
## Analyze codebase before setup
When setting up a new service from source, detect the project type from marker files:
| Marker file | Type |
|---|---|
| `package.json` | Node.js |
| `requirements.txt` or `pyproject.toml` | Python |
| `go.mod` | Go |
| `Cargo.toml` | Rust |
| `index.html` (no package.json) | Static site |
### Monorepo detection
| Marker | Monorepo type |
|---|---|
| `pnpm-workspace.yaml` | pnpm workspace (shared) |
| `package.json` with `workspaces` field | npm/yarn workspace (shared) |
| `turbo.json` | Turborepo (shared) |
| Multiple subdirectories with separate `package.json`, no workspace config | Isolated monorepo |
**Isolated monorepo** (apps don't share code): set `rootDirectory` to the app's subdirectory (for example, `/apps/api`).
**Shared monorepo** (TypeScript workspaces, shared packages): do not set `rootDirectory`. Set custom build and start commands instead:
- pnpm: `pnpm --filter <package> build`
- npm: `npm run build --workspace=packages/<package>`
- yarn: `yarn workspace <package> build`
- Turborepo: `turbo run build --filter=<package>`
### Scaffolding hints
When no code exists, minimal starting points for common types:
- **Static site**: create `index.html` in the root directory.
- **Vite React**: `npm create vite@latest . -- --template react`
- **Python FastAPI**: create `main.py` with a FastAPI app and `requirements.txt` with `fastapi` and `uvicorn`.
- **Go**: create `main.go` with an HTTP server that reads `PORT` from the environment.
## Workspaces
Workspaces scope billing and team access. Most users have one personal workspace and possibly team workspaces.
```bash
railway whoami --json # lists workspace memberships
```
When creating projects, Railway uses the default workspace unless `--workspace` is specified.
## Troubleshoot setup issues
- **CLI missing**: install via `brew install railway` or `curl -fsSL https://railway.com/install.sh | sh`
- **Not authenticated**: `railway login`
- **Project not found**: verify with `railway project list --json`, check workspace context
- **Service not found**: `railway service status --all --json` to list all services in the project
- **Wrong workspace**: inspect `railway whoami --json`, re-run with explicit `--workspace`
- **Permission denied**: check workspace role, mutations require member or admin access
## Validated against
- Docs: [cli.md](https://docs.railway.com/cli), [init.md](https://docs.railway.com/cli/init), [add.md](https://docs.railway.com/cli/add), [link.md](https://docs.railway.com/cli/link), [project.md](https://docs.railway.com/cli/project), [list.md](https://docs.railway.com/cli/list), [whoami.md](https://docs.railway.com/cli/whoami)
- CLI source: [init.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/init.rs), [add.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/add.rs), [project.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/project.rs), [list.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/list.rs), [bucket.rs](https://github.com/railwayapp/cli/blob/feat/bucket-command/src/commands/bucket.rs)
```
### references/deploy.md
```markdown
# Deploy
Ship code, manage releases, and configure builds.
## Deploy code
### Standard deploy
```bash
railway up --detach -m "<release summary>"
```
`--detach` returns immediately instead of streaming build logs. Without it, the deploy blocks execution until the build finishes. Always include `-m` with a release summary for auditability.
### Watch the build
```bash
railway up --ci -m "<release summary>"
```
`--ci` streams build logs and exits when the build completes. Use this when the user wants to see build output or when you need to triage build failures immediately.
### Targeted deploy
When multiple services exist, target explicitly:
```bash
railway up --service <service> --environment <environment> --detach -m "<summary>"
```
### Deploy to an unlinked project
For CI or cross-project deploys where the directory isn't linked:
```bash
railway up --project <project-id> --environment <environment> --detach -m "<summary>"
```
`--project` requires `--environment`. Railway needs both to resolve context.
## Manage releases
### Redeploy and restart
```bash
railway redeploy --service <service> --yes # rebuild and deploy from same source
railway restart --service <service> --yes # restart without rebuilding
```
Redeploy triggers a full build cycle. Restart only restarts the running container. Use restart when the code hasn't changed but the service needs a fresh process (for example, after variable changes).
### Remove latest deployment
```bash
railway down --service <service> --yes
```
This removes the latest successful deployment but doesn't delete the service. To delete a service entirely, use environment config patching (see [configure.md](configure.md)).
## Deployment history and logs
```bash
railway deployment list --service <service> --limit 20 --json
railway logs --service <service> --lines 200 --json # runtime logs
railway logs --service <service> --build --lines 200 --json # build logs
railway logs --latest --lines 200 --json # latest deployment
```
`railway logs` streams indefinitely when no bounding flags are given. An open stream blocks execution and never returns. Always use `--lines`, `--since`, or `--until` to get a bounded fetch.
## Build configuration
Railway uses Railpack as the default builder. It detects language and framework from repo contents and assembles a build plan automatically.
### Builder selection
Three builder options, set via service config:
- **RAILPACK** auto-detects language and framework, builds from source (default)
- **NIXPACKS** is the legacy builder. DO NOT USE THIS, use RAILPACK instead.
- **DOCKERFILE** uses a Dockerfile you provide
```bash
railway environment edit --service-config <service> build.builder RAILPACK
railway environment edit --service-config <service> build.builder DOCKERFILE
railway environment edit --service-config <service> build.dockerfilePath "docker/Dockerfile.prod"
```
### Build and start commands
Override when auto-detection gets it wrong:
```bash
railway environment edit --service-config <service> build.buildCommand "npm run build"
railway environment edit --service-config <service> deploy.startCommand "npm start"
```
Common reasons to override: wrong package manager detected, multiple build targets in a monorepo, framework-specific output paths.
### Railpack environment variables
Control Railpack behavior by setting these as service variables:
| Variable | Purpose |
|---|---|
| `RAILPACK_NODE_VERSION` | Pin Node.js version (e.g., `20`, `22.1.0`) |
| `RAILPACK_PYTHON_VERSION` | Pin Python version (e.g., `3.12`) |
| `RAILPACK_GO_BIN` | Go binary name to build |
| `RAILPACK_STATIC_FILE_ROOT` | Directory for static site output (e.g., `dist`, `build`) |
| `RAILPACK_SPA_OUTPUT_DIR` | SPA output directory with client-side routing support |
| `RAILPACK_PACKAGES` | Additional system packages for the build |
| `RAILPACK_BUILD_APT_PACKAGES` | Apt packages available during build only |
| `RAILPACK_DEPLOY_APT_PACKAGES` | Apt packages available at runtime only |
For full Railpack documentation including language-specific detection, config files, and framework support: https://railpack.com/llms.txt
### Static sites
Railpack detects static sites from `Staticfile`, `index.html`, or `RAILPACK_STATIC_FILE_ROOT` and serves them with a built-in static file server. If the build outputs to a non-standard directory (for example, `dist/`, `build/`), set `RAILPACK_STATIC_FILE_ROOT` as a variable so Railpack knows where to find the output.
## Monorepo patterns
### Isolated monorepo
When services don't share code, isolate each with its own root directory:
```bash
railway environment edit --service-config <service> source.rootDirectory "/packages/api"
```
Each service sees only its subdirectory. This approach is clean but breaks if services import from shared packages.
### Shared monorepo
When services depend on shared packages or root-level workspace config, keep the full repo context and scope via build/start commands instead:
```bash
# pnpm workspaces
railway environment edit --service-config <service> build.buildCommand "pnpm --filter api build"
railway environment edit --service-config <service> deploy.startCommand "pnpm --filter api start"
# yarn workspaces
railway environment edit --service-config <service> build.buildCommand "yarn workspace api build"
railway environment edit --service-config <service> deploy.startCommand "yarn workspace api start"
# bun workspaces
railway environment edit --service-config <service> build.buildCommand "bun run --filter api build"
railway environment edit --service-config <service> deploy.startCommand "bun run --filter api start"
# turborepo (works with any package manager)
railway environment edit --service-config <service> build.buildCommand "npx turbo run build --filter=api"
railway environment edit --service-config <service> deploy.startCommand "npx turbo run start --filter=api"
```
Don't set a restrictive `rootDirectory` in this case. The build needs access to the workspace root.
### Watch paths
Prevent unrelated package changes from redeploying every service:
```bash
railway environment edit --service-config <service> build.watchPatterns '["packages/api/**","packages/shared/**"]'
```
### Common monorepo pitfalls
- **Using `rootDirectory` with shared imports**: if service A imports from `packages/shared/`, setting `rootDirectory: "/packages/a"` hides the shared code. Use the shared monorepo pattern instead.
- **Forgetting watch paths**: without watch paths, every push redeploys all services, even when only one package changed.
- **Wrong filter target**: `pnpm --filter api` uses the `name` field in each package's `package.json`, not the directory name. Verify the package name matches.
## Troubleshoot deploys
- **No project/service context**: run `railway link` or pass `--project` with `--environment`
- **Build fails before compile**: check dependency graph, lockfiles, and whether the right builder is selected
- **Build succeeds but app crashes**: verify start command and required runtime variables
- **Wrong files in build**: check root directory and watch patterns
- **`railway down` treated as delete**: `down` only removes the latest deployment. For full service deletion, use `isDeleted` in config patch (see [configure.md](configure.md))
- **Wrong Node/Python version detected**: set `RAILPACK_NODE_VERSION` or `RAILPACK_PYTHON_VERSION` as a service variable to pin the version
- **Missing system package at runtime**: add the package to `RAILPACK_DEPLOY_APT_PACKAGES`
## Validated against
- Docs: [up.md](https://docs.railway.com/cli/up), [deploying.md](https://docs.railway.com/cli/deploying), [deployment.md](https://docs.railway.com/cli/deployment), [down.md](https://docs.railway.com/cli/down), [railpack.md](https://docs.railway.com/builds/railpack), [monorepo.md](https://docs.railway.com/deployments/monorepo)
- CLI source: [up.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/up.rs), [deployment.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/deployment.rs), [down.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/down.rs), [redeploy.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/redeploy.rs), [restart.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/restart.rs)
```
### references/configure.md
```markdown
# Configure
Manage environments, variables, service config, domains, and networking.
## Environments
### List and switch
```bash
railway environment list --json
railway environment link <environment> # switch active environment
```
### Create
```bash
railway environment new <name>
railway environment new <name> --duplicate <source-environment> # clone config from existing
```
Duplicating copies all service configurations and variables from the source environment.
## Variables
### Read, set, and delete
```bash
railway variable list --service <service> --environment <env> --json
railway variable set KEY=value --service <service> --environment <env>
railway variable delete KEY --service <service> --environment <env>
```
Variable changes trigger a redeployment by default. This is usually the desired behavior, since the service picks up the values on restart.
### Template syntax
Railway supports interpolation between services and shared variables:
```text
${{KEY}} # same-service variable
${{shared.API_KEY}} # shared variable
${{postgres.DATABASE_URL}} # variable from another service
${{api.RAILWAY_PRIVATE_DOMAIN}} # another service's private domain
```
Wiring example, a frontend connecting to a backend over private networking:
```text
BACKEND_URL=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}
```
### Wiring services together
Each managed database creates connection variables automatically. Reference them from other services using template syntax:
| Database | Variable reference |
|---|---|
| Postgres | `${{Postgres.DATABASE_URL}}` |
| Redis | `${{Redis.REDIS_URL}}` |
| MySQL | `${{MySQL.MYSQL_URL}}` |
| MongoDB | `${{MongoDB.MONGO_URL}}` |
Service names in references are case-sensitive and must match the service name exactly as it appears in the project.
**Public vs private networking decision:**
| Traffic path | Use |
|---|---|
| Browser → API | Public domain |
| Service → Service | Private domain (`RAILWAY_PRIVATE_DOMAIN`) |
| Service → Database | Private (automatic, uses internal DNS) |
**Frontend apps cannot use private networking.** Frontends run in the user's browser, not on Railway's network. They cannot reach `RAILWAY_PRIVATE_DOMAIN` or internal database URLs. Options:
1. **Backend proxy** (recommended): frontend calls a backend API on a public domain, backend connects to the database over the private network.
2. **Public database URL**: use the public connection variable (for example, `${{Postgres.DATABASE_PUBLIC_URL}}`). This requires a TCP proxy on the database service and exposes the database to the internet — only appropriate for development or low-sensitivity data.
### Railway-provided variables
These are set automatically at runtime. Availability depends on resource configuration.
**Networking:**
| Variable | Available when |
|---|---|
| `RAILWAY_PUBLIC_DOMAIN` | Public domain is configured |
| `RAILWAY_PRIVATE_DOMAIN` | Always (internal DNS for service-to-service traffic) |
| `RAILWAY_TCP_PROXY_DOMAIN` | TCP proxy is enabled |
| `RAILWAY_TCP_PROXY_PORT` | TCP proxy is enabled |
**Context:**
| Variable | Available when |
|---|---|
| `RAILWAY_PROJECT_ID` | Always |
| `RAILWAY_ENVIRONMENT_ID` | Always |
| `RAILWAY_ENVIRONMENT_NAME` | Always |
| `RAILWAY_SERVICE_ID` | Always |
| `RAILWAY_SERVICE_NAME` | Always |
| `RAILWAY_DEPLOYMENT_ID` | Always |
| `RAILWAY_REPLICA_ID` | Replicas configured |
| `RAILWAY_REPLICA_REGION` | Multi-region configured |
**Git (present when deployed from a linked repo):**
| Variable | Description |
|---|---|
| `RAILWAY_GIT_COMMIT_SHA` | Full commit hash of the deployed revision |
| `RAILWAY_GIT_AUTHOR` | Commit author name |
| `RAILWAY_GIT_COMMIT_MESSAGE` | First line of the commit message |
| `RAILWAY_GIT_BRANCH` | Branch that triggered the deploy |
**Storage (present when a volume is attached):**
| Variable | Description |
|---|---|
| `RAILWAY_VOLUME_MOUNT_PATH` | Filesystem path where the volume is mounted |
| `RAILWAY_VOLUME_NAME` | Name of the attached volume |
Sealed variables are write-only. Their values don't appear in CLI output.
## Service config
Service configuration controls source, build, deploy, and networking settings. There are two ways to mutate it.
### Dot-path patch
For single-field changes:
```bash
railway environment edit --service-config <service> deploy.startCommand "npm start"
railway environment edit --service-config <service> build.buildCommand "npm run build"
railway environment edit --service-config <service> source.rootDirectory "/apps/api"
railway environment edit --service-config <service> deploy.numReplicas 2
```
### JSON patch
For multi-field changes or complex structures:
```bash
railway environment edit --json <<'JSON'
{"services":{"<service-id>":{"build":{"buildCommand":"npm run build"},"deploy":{"startCommand":"npm start"}}}}
JSON
```
Resolve exact service IDs from `railway status --json` before constructing JSON patches. Using names in the JSON payload doesn't work.
### Config schema (typed paths)
Include only keys you're changing. The full shape:
**Source**: `source.image` (string), `source.repo` (string), `source.branch` (string), `source.rootDirectory` (string), `source.checkSuites` (boolean), `source.commitSha` (string), `source.autoUpdates.type` (string: `disabled`, `patch`, `minor`)
**Build**: `build.builder` (string: `RAILPACK`, `NIXPACKS`, `DOCKERFILE`), `build.buildCommand` (string), `build.dockerfilePath` (string), `build.watchPatterns` (string array), `build.nixpacksConfigPath` (string)
**Deploy**: `deploy.startCommand` (string), `deploy.preDeployCommand` (string), `deploy.healthcheckPath` (string), `deploy.healthcheckTimeout` (integer), `deploy.numReplicas` (integer), `deploy.restartPolicyType` (string: `ON_FAILURE`, `ALWAYS`, `NEVER`), `deploy.restartPolicyMaxRetries` (integer), `deploy.sleepApplication` (boolean), `deploy.cronSchedule` (string), `deploy.multiRegionConfig` (object)
**Multi-region config** structure for `deploy.multiRegionConfig`:
```json
{ "us-west2": { "numReplicas": 2 }, "europe-west4-drams3a": { "numReplicas": 1 } }
```
| Region identifier | Location |
|---|---|
| `us-west2` | US West (Oregon) |
| `us-east4-eqdc4a` | US East (Virginia) |
| `europe-west4-drams3a` | Europe (Netherlands) |
| `asia-southeast1-eqsg3a` | Asia (Singapore) |
Natural language mapping: "add replicas in Europe" → `europe-west4-drams3a`, "US East" → `us-east4-eqdc4a`. When the user doesn't specify a region, query current config first with `railway environment config --json` to see existing region assignments before modifying.
**Variables**: `variables.<KEY>.value` (string), `variables.<KEY>.isOptional` (boolean), `variables.<KEY>.isSealed` (boolean). Delete a variable by setting it to `null`.
**Lifecycle**: `isDeleted` (boolean) removes the service. `isCreated` (boolean) marks as new.
**Storage**: `volumeMounts.<volume-id>.mountPath` (string), `volumes.<volume-id>.isDeleted` (boolean)
**Buckets**: `buckets.<bucket-id>.region` (string: `sjc`, `iad`, `ams`, `sin`), `buckets.<bucket-id>.isCreated` (boolean), `buckets.<bucket-id>.isDeleted` (boolean). Buckets are created at the project level via `railway bucket create` and deployed to environments via config patches. The CLI handles this automatically — use `railway bucket` commands
### Shared variables and project-level config
```bash
railway environment edit --json <<'JSON'
{"sharedVariables":{"API_BASE":{"value":"https://example.com"}}}
JSON
```
Shared variables are accessible from any service via `${{shared.KEY}}`.
### Read config
Always inspect before mutating. Config patches merge, so you need to know the state to avoid overwriting fields unintentionally:
```bash
railway environment config --json
```
Verify after mutation to confirm the change took effect:
```bash
railway environment config --json
railway service status --all --json
```
## Domains
### Railway domain
One Railway-provided domain per service, generated automatically:
```bash
railway domain --service <service> --json
```
### Custom domain
```bash
railway domain example.com --service <service> --json
```
This returns the DNS records you need to configure at your DNS provider. Multiple custom domains per service are supported.
### Target port
If the service listens on a non-default port:
```bash
railway domain example.com --service <service> --port 8080 --json
```
### Private networking
For service-to-service traffic within a project, use private domain references instead of public URLs. This avoids egress and is faster:
```text
BACKEND_URL=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}
```
### Read current domains
Domain configuration lives in `config.services.<service-id>.networking` under `serviceDomains` (Railway-provided) and `customDomains`. Inspect with:
```bash
railway environment config --json
```
### Remove a domain
Remove domains via JSON config patch by setting the domain ID to `null`:
**Remove a custom domain:**
```bash
railway environment edit --json <<'JSON'
{"services":{"<service-id>":{"networking":{"customDomains":{"<domain-id>":null}}}}}
JSON
```
**Remove a Railway-provided domain:**
```bash
railway environment edit --json <<'JSON'
{"services":{"<service-id>":{"networking":{"serviceDomains":{"<domain-id>":null}}}}}
JSON
```
Get the domain IDs from `railway environment config --json` under the service's `networking` object.
## Troubleshoot configuration
- **Invalid dot-path**: check field names and types in the config schema section above
- **Wrong service key in JSON patch**: resolve service IDs from `railway status --json`
- **Variable change didn't take effect**: verify with `railway variable list`, changes trigger redeploy by default
- **Domain returns errors**: verify the service has a healthy deployment and the target port is correct
- **DNS propagation delay**: custom domains take time to propagate, this is normal
- **Cloudflare proxy issues**: align SSL/TLS mode per Railway's domain guidance
- **Private networking failing**: verify the service is listening on the referenced port and that the private domain variable reference is correct
- **Multi-region patch ignored**: verify region names match the exact identifiers (`us-west2`, `us-east4-eqdc4a`, `europe-west4-drams3a`, `asia-southeast1-eqsg3a`)
## Validated against
- Docs: [environment.md](https://docs.railway.com/cli/environment), [variable.md](https://docs.railway.com/cli/variable), [variables.md](https://docs.railway.com/variables), [domains.md](https://docs.railway.com/networking/domains), [public-networking.md](https://docs.railway.com/networking/public-networking), [private-networking.md](https://docs.railway.com/networking/private-networking)
- CLI source: [environment/mod.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/environment/mod.rs), [environment/edit.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/environment/edit.rs), [variable.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/variable.rs), [domain.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/domain.rs), [controllers/config/patch.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/controllers/config/patch.rs)
```
### references/operate.md
```markdown
# Operate
Check health, read logs, query metrics, and troubleshoot failures.
## Health snapshot
Start broad, then narrow:
```bash
railway status --json # linked context
railway service status --all --json # all services, deployment states
railway deployment list --limit 10 --json # recent deployments
```
Deployment statuses: `SUCCESS`, `BUILDING`, `DEPLOYING`, `FAILED`, `CRASHED`, `REMOVED`.
For projects with buckets, include bucket status:
```bash
railway bucket list --json # buckets in current environment
railway bucket info --bucket <name> --json # storage size, object count, region
```
If everything looks healthy, return a summary and stop. If something is degraded or failing, continue to log inspection.
## Logs
### Recent logs
```bash
railway logs --service <service> --lines 200 --json # runtime logs
railway logs --service <service> --build --lines 200 --json # build logs
railway logs --latest --lines 200 --json # latest deployment
```
`railway logs` streams indefinitely when no bounding flags are given. Always use `--lines`, `--since`, or `--until` to get a bounded fetch. Open streams block execution.
### Time-bounded queries
```bash
railway logs --service <service> --since 1h --lines 400 --json
railway logs --service <service> --since 30m --until 10m --lines 400 --json
```
### Filtered queries
Use `--filter` to narrow logs without scanning everything manually:
```bash
railway logs --service <service> --lines 200 --filter "@level:error" --json
railway logs --service <service> --lines 200 --filter "@level:warn AND timeout" --json
railway logs --service <service> --lines 200 --filter "connection refused" --json
```
Filter syntax supports text search (`"error message"`), attribute filters (`@level:error`, `@level:warn`), and boolean operators (`AND`, `OR`, `-` for negation). Full syntax: https://docs.railway.com/guides/logs
### Scoped by environment
```bash
railway logs --service <service> --environment <env> --lines 200 --json
```
## Metrics
Resource usage metrics (CPU, memory, network, disk) are only available through the GraphQL API:
```bash
scripts/railway-api.sh \
'query metrics($environmentId: String!, $serviceId: String, $startDate: DateTime!, $groupBy: [MetricTag!], $measurements: [MetricMeasurement!]!) {
metrics(environmentId: $environmentId, serviceId: $serviceId, startDate: $startDate, groupBy: $groupBy, measurements: $measurements) {
measurement tags { serviceId deploymentId region } values { ts value }
}
}' \
'{"environmentId":"<env-id>","serviceId":"<service-id>","startDate":"2026-02-19T00:00:00Z","measurements":["CPU_USAGE","MEMORY_USAGE_GB"]}'
```
Available measurements: `CPU_USAGE`, `CPU_LIMIT`, `MEMORY_USAGE_GB`, `MEMORY_LIMIT_GB`, `NETWORK_RX_GB`, `NETWORK_TX_GB`, `DISK_USAGE_GB`, `EPHEMERAL_DISK_USAGE_GB`, `BACKUP_USAGE_GB`.
Omit `serviceId` and add `"groupBy": ["SERVICE_ID"]` to query all services in the environment at once. Get the environment and service IDs from `railway status --json`.
## Failure triage
When something is broken, classify the failure first. The fix depends on the class.
### Build failures
The service failed to build. Look at build logs:
```bash
railway logs --latest --build --lines 400 --json
```
Common causes and fixes:
- **Missing dependencies**: check lockfiles, verify package manager detection
- **Wrong build command**: override with `railway environment edit --service-config <service> build.buildCommand "<command>"`
- **Builder mismatch**: switch builders with `railway environment edit --service-config <service> build.builder RAILPACK`
- **Wrong root directory** (monorepo): set `source.rootDirectory` to the correct package path
### Runtime failures
The build succeeded but the service crashes or misbehaves:
```bash
railway logs --latest --lines 400 --json
railway logs --service <service> --since 1h --lines 400 --json
```
Common causes and fixes:
- **Bad start command**: override with `railway environment edit --service-config <service> deploy.startCommand "<command>"`
- **Missing runtime variable**: check `railway variable list --service <service> --json` and set missing values
- **Port mismatch**: the service must listen on `$PORT` (Railway injects this). Verify with logs.
- **Upstream dependency down**: check other services' status and logs
### Config-driven failures
Something worked before and broke after a config change:
```bash
railway environment config --json
railway variable list --service <service> --json
```
Compare the config against expected values. Look for changes that may have introduced the regression.
### Networking failures
Domain returns errors, or service-to-service calls fail:
```bash
railway domain --service <service> --json
railway logs --service <service> --lines 200 --json
```
Check: target port matches what the service listens on, domain status is healthy, private domain variable references are correct.
## Recovery
After identifying the cause, fix and verify:
```bash
# Fix (examples)
railway environment edit --service-config <service> deploy.startCommand "<correct-command>"
railway variable set MISSING_VAR=value --service <service>
# Redeploy
railway redeploy --service <service> --yes
# Verify
railway service status --service <service> --json
railway logs --service <service> --lines 200 --json
```
Always verify after fixing. Don't assume the redeploy succeeded.
## Troubleshoot common blockers
- **Unlinked context**: `railway link --project <id-or-name>`
- **Missing service scope for logs**: pass `--service` and `--environment` explicitly
- **No deployments found**: the service exists but has never deployed, create an initial deploy first
- **Metrics query returns empty**: verify IDs from `railway status --json` and check the time window
- **Config patch type error**: check the typed paths in [configure.md](configure.md), for example, `numReplicas` is an integer, not a string
## Validated against
- Docs: [status.md](https://docs.railway.com/cli/status), [logs.md](https://docs.railway.com/cli/logs), [observability.md](https://docs.railway.com/observability), [observability/logs.md](https://docs.railway.com/observability/logs), [observability/metrics.md](https://docs.railway.com/observability/metrics)
- CLI source: [status.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/status.rs), [logs.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/logs.rs), [deployment.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/deployment.rs), [redeploy.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/redeploy.rs)
```
### references/request.md
```markdown
# Request
Official documentation and community endpoints. GraphQL operations for things the CLI doesn't expose.
## Official documentation
Primary sources for authoritative Railway information:
- **Full LLM docs**: `https://docs.railway.com/api/llms-docs.md`
- **LLM summary**: `https://railway.com/llms.txt`
- **Templates**: `https://railway.com/llms-templates.md`
- **Changelog**: `https://railway.com/llms-changelog.md`
- **Blog**: `https://blog.railway.com/llms-blog.md`
- **Direct doc pages**: `https://docs.railway.com/<path>` (for example, `cli/up`, `networking/domains`, `observability/logs`)
Tip: append `.md` to any `docs.railway.com` page URL to get a markdown version suitable for LLM consumption.
Common doc paths:
| Topic | Path |
|---|---|
| Projects | `guides/projects` |
| Deployments | `guides/deployments` |
| Volumes | `guides/volumes` |
| Variables | `guides/variables` |
| CLI reference | `reference/cli-api` |
| Pricing | `reference/pricing` |
| Public networking | `networking/public-networking` |
| Private networking | `networking/private-networking` |
Fetch official docs first for product behavior questions. Use Central Station only when you need community evidence, prior incidents, or implementation anecdotes.
## Central Station (community)
Search and browse Railway's community platform for prior discussions, issue patterns, and field solutions.
### Recent threads
```bash
curl -s 'https://station-server.railway.com/gql' \
-H 'content-type: application/json' \
-d '{"query":"{ threads(first: 10, sort: recent_activity) { edges { node { slug subject status upvoteCount createdAt topic { slug displayName } } } } }"}'
```
Filter by topic with the `topic` parameter (`"questions"`, `"feedback"`, `"community"`, `"billing"`):
```bash
curl -s 'https://station-server.railway.com/gql' \
-H 'content-type: application/json' \
-d '{"query":"{ threads(first: 10, sort: recent_activity, topic: \"questions\") { edges { node { slug subject status topic { displayName } upvoteCount } } } }"}'
```
Sort options: `recent_activity` (default), `newest`, `highest_votes`.
### Search threads
```bash
curl -s 'https://station-server.railway.com/gql' \
-H 'content-type: application/json' \
-d '{"query":"{ threads(first: 10, search: \"<search-term>\") { edges { node { slug subject status } } } }"}'
```
### LLM data export
Bulk search alternative — fetches all public threads with full content:
```bash
curl -s 'https://station-server.railway.com/api/llms-station'
```
### Read a full thread
```bash
curl -s 'https://station-server.railway.com/api/threads/<slug>?format=md'
```
Thread URLs follow the format: `https://station.railway.com/{topic_slug}/{thread_slug}`
Community threads are anecdotal. Always pair with official docs when the answer informs an operational decision.
## GraphQL helper
All GraphQL operations use the API helper script, which handles authentication automatically:
```bash
scripts/railway-api.sh '<query>' '<variables-json>'
```
The script reads the API token from `~/.railway/config.json` and sends requests to `https://backboard.railway.com/graphql/v2`.
For the full API schema, see: https://docs.railway.com/api/llms-docs.md
## Project mutations
The CLI doesn't expose project setting updates (rename, PR deploys, visibility). Use GraphQL:
```bash
scripts/railway-api.sh \
'mutation updateProject($id: String!, $input: ProjectUpdateInput!) {
projectUpdate(id: $id, input: $input) { id name isPublic prDeploys botPrEnvironments }
}' \
'{"id":"<project-id>","input":{"name":"new-name","prDeploys":true}}'
```
Common `ProjectUpdateInput` fields: `name`, `isPublic`, `prDeploys`, `botPrEnvironments`.
## Service mutations
The CLI can create services (`railway add`) but cannot rename them or change icons. Use GraphQL:
```bash
scripts/railway-api.sh \
'mutation updateService($id: String!, $input: ServiceUpdateInput!) {
serviceUpdate(id: $id, input: $input) { id name icon }
}' \
'{"id":"<service-id>","input":{"name":"new-name"}}'
```
`ServiceUpdateInput` fields: `name`, `icon` (image URL, animated GIF, or devicons URL like `https://devicons.railway.app/postgres`).
Get the service ID from `railway status --json`.
## Service creation via GraphQL
Prefer `railway add` for most cases. Use GraphQL for programmatic or advanced use:
```bash
scripts/railway-api.sh \
'mutation createService($input: ServiceCreateInput!) {
serviceCreate(input: $input) { id name }
}' \
'{"input":{"projectId":"<project-id>","name":"my-service","source":{"image":"nginx:latest"}}}'
```
`ServiceCreateInput` fields:
| Field | Type | Description |
|---|---|---|
| `projectId` | String! | Target project (required) |
| `name` | String | Service name (auto-generated if omitted) |
| `source.image` | String | Docker image (for example, `nginx:latest`) |
| `source.repo` | String | GitHub repo (for example, `user/repo`) |
| `branch` | String | Git branch for repo source |
| `environmentId` | String | Create only in a specific environment |
After creating a service via GraphQL, configure it with a JSON config patch including `isCreated: true` (see [configure.md](configure.md)).
## Metrics queries
Resource usage metrics are only available via GraphQL:
```bash
scripts/railway-api.sh \
'query metrics($environmentId: String!, $serviceId: String, $startDate: DateTime!, $endDate: DateTime, $sampleRateSeconds: Int, $averagingWindowSeconds: Int, $groupBy: [MetricTag!], $measurements: [MetricMeasurement!]!) {
metrics(environmentId: $environmentId, serviceId: $serviceId, startDate: $startDate, endDate: $endDate, sampleRateSeconds: $sampleRateSeconds, averagingWindowSeconds: $averagingWindowSeconds, groupBy: $groupBy, measurements: $measurements) {
measurement tags { serviceId deploymentId region } values { ts value }
}
}' \
'{"environmentId":"<env-id>","serviceId":"<service-id>","startDate":"2026-02-19T00:00:00Z","measurements":["CPU_USAGE","MEMORY_USAGE_GB"]}'
```
Available `MetricMeasurement` values: `CPU_USAGE`, `CPU_LIMIT`, `MEMORY_USAGE_GB`, `MEMORY_LIMIT_GB`, `NETWORK_RX_GB`, `NETWORK_TX_GB`, `DISK_USAGE_GB`, `EPHEMERAL_DISK_USAGE_GB`, `BACKUP_USAGE_GB`.
Optional parameters: `endDate` (defaults to now), `sampleRateSeconds`, `averagingWindowSeconds`. Use `groupBy: ["SERVICE_ID"]` without `serviceId` to query all services in an environment at once. Valid `MetricTag` values for `groupBy`: `SERVICE_ID`, `DEPLOYMENT_ID`, `DEPLOYMENT_INSTANCE_ID`, `REGION`.
Get environment and service IDs from `railway status --json`.
## Template search
Search Railway's template marketplace:
```bash
scripts/railway-api.sh \
'query templates($query: String!, $verified: Boolean, $recommended: Boolean) {
templates(query: $query, verified: $verified, recommended: $recommended) {
edges { node { code name description category } }
}
}' \
'{"query":"redis","verified":true}'
```
| Parameter | Type | Description |
|---|---|---|
| `query` | String | Search term |
| `verified` | Boolean | Only verified templates |
| `recommended` | Boolean | Only recommended templates |
| `first` | Int | Number of results (max ~100) |
Common template codes: `ghost`, `strapi`, `minio`, `n8n`, `uptime-kuma`, `umami`, `postgres`, `redis`, `mysql`, `mongodb`.
Deploy a found template via CLI:
```bash
railway deploy --template <template-code>
```
### GraphQL template deployment
For deploying into a specific environment or tracking the workflow, use the two-step GraphQL flow:
**Step 1** — Fetch the template config:
```bash
scripts/railway-api.sh \
'query template($code: String!) {
template(code: $code) { id serializedConfig }
}' \
'{"code":"postgres"}'
```
**Step 2** — Deploy with `templateDeployV2`:
```bash
scripts/railway-api.sh \
'mutation deploy($input: TemplateDeployV2Input!) {
templateDeployV2(input: $input) { projectId workflowId }
}' \
'{"input":{
"templateId":"<id-from-step-1>",
"serializedConfig":<config-object-from-step-1>,
"projectId":"<project-id>",
"environmentId":"<env-id>",
"workspaceId":"<workspace-id>"
}}'
```
`serializedConfig` is the raw JSON object from the template query, not a string. Get `workspaceId` via `scripts/railway-api.sh 'query { project(id: "<project-id>") { workspaceId } }' '{}'`.
## Validated against
- Docs: [api docs](https://docs.railway.com/api/llms-docs.md), [community.md](https://docs.railway.com/community), [cli/docs.md](https://docs.railway.com/cli/docs)
- CLI source: [docs.rs](https://github.com/railwayapp/cli/blob/a8a5afe/src/commands/docs.rs)
```
### scripts/railway-api.sh
```bash
#!/usr/bin/env bash
# Railway GraphQL API helper
# Usage: railway-api.sh '<graphql-query>' ['<variables-json>']
set -e
if ! command -v jq &>/dev/null; then
echo '{"error": "jq not installed. Install with: brew install jq"}'
exit 1
fi
CONFIG_FILE="$HOME/.railway/config.json"
if [[ ! -f "$CONFIG_FILE" ]]; then
echo '{"error": "Railway config not found. Run: railway login"}'
exit 1
fi
TOKEN=$(jq -r '.user.token' "$CONFIG_FILE")
if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then
echo '{"error": "No Railway token found. Run: railway login"}'
exit 1
fi
if [[ -z "$1" ]]; then
echo '{"error": "No query provided"}'
exit 1
fi
# Build payload with query and optional variables
if [[ -n "$2" ]]; then
PAYLOAD=$(jq -n --arg q "$1" --argjson v "$2" '{query: $q, variables: $v}')
else
PAYLOAD=$(jq -n --arg q "$1" '{query: $q}')
fi
curl -s https://backboard.railway.com/graphql/v2 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "$PAYLOAD"
```