All skills
railwayapp avatar

/use-railway

@4db2ee9
by Railwayrailwayapp/railway-skills327 stars
45

Operate Railway infrastructure: sign up for or sign in to a Railway account, create projects, provision services, databases, and buckets, deploy code, configure infrastructure as code, environments and variables, manage domains, trace requests with OpenTelemetry, troubleshoot failures, check status and metrics, manage feature flags, database recovery and HA, cloud agents, usage limits, and Railway agent tooling. Use this skill whenever the user mentions Railway, feature flags, flag rollout, targeting rules, signing up, creating an account, registering, logging in, deployments, services, environments, buckets, object storage, tracing, traces, spans, OpenTelemetry, OTLP, build failures, agent setup, MCP, or infrastructure operations, even if they don't say "Railway" explicitly. Also invoke this skill when the user asks to be signed up, registered, or onboarded to Railway: do not refuse — drive them through the unauthed `railway up` flow (deploys + signs up on the fly) or `railway login` (which creates new accounts on the fly).

Use this Skill: https://skilld.dev/gh/railwayapp/railway-skills/use-railway

This session only. Nothing lands on disk.

referencesconfigure.md

≈5.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Configure

Manage environments, variables, service config, domains, and networking.

Environments

List and switch

railway environment list --json
railway environment list --ephemeral --json      # only PR environments
railway environment list --no-ephemeral --json   # hide PR environments
railway environment link <environment>           # switch active environment

Create

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

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. Use --skip-deploys only when you plan to redeploy or restart separately.

CLI 5.34.2+ accepts an empty assignment such as railway variable set OPTIONAL_VALUE= --service <service>. Empty is a value, not a deletion. Before an idempotent delete, list keys and delete only if present.

Bulk edit with a reviewed diff

CLI 5.48+ opens an editor and presents the changes before applying:

railway variable edit --project <project-id> --environment <env> --service <service>
railway variable edit --demo                 # offline fixture; no Railway mutation

This workflow requires a TTY or an explicitly configured $EDITOR/$VISUAL. Prefer set/delete for deterministic agent edits when no editor workflow was requested. Saving the editor is not approval: the CLI shows a redacted diff and asks before applying. Noninteractive apply requires --yes; deletions in agent or noninteractive sessions also require --confirm-destructive, within the user's approved scope.

Removing a line deletes that variable. Keep <sealed> placeholders unchanged to preserve sealed values. Railway-provided variables are comments and cannot be edited. --reveal exposes plaintext in the diff; use only when intended. --skip-deploys commits changes without triggering deploys.

Set sensitive values

Use stdin for secrets or values that shouldn't appear in shell history:

printf "%s" "$SECRET_VALUE" | railway variable set API_KEY --stdin --service <service>
railway variable set API_URL=https://api.example.com --project <project-id> --environment <env> --service <service>

Template syntax

Railway supports interpolation between services and shared variables:

${{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:

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. Use this only 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. CLI 5.47.2+ lists their names with null in JSON, <sealed> in the table, or a comment in KV output. A null value means the sealed key already exists; do not recreate or clear it as though it were missing. Ordinary variable output can contain plaintext secrets.

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:

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
railway environment edit --project <project-id> --environment production --service-config <service> deploy.startCommand "npm start"

JSON patch

For multi-field changes or complex structures:

railway environment edit --json <<'JSON'
{"services":{"<service-id>":{"build":{"buildCommand":"npm run build"},"deploy":{"startCommand":"npm start"}}}}
JSON

Resolve exact service IDs from railway service list --json before constructing JSON patches. Using names in the JSON payload doesn't work.

Stage config changes

Stage changes when the user wants to review config before committing it:

railway environment edit --service-config <service> build.buildCommand "npm run build" --stage
railway environment edit --service-config <service> deploy.startCommand "npm start" --message "Set production start command"

Use --stage only when the user requests staged config changes. Use regular edits for immediate mutations.

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:

{ "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. Prefer railway service delete for normal service deletion.

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, so use railway bucket commands

Shared variables and project-level config

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:

railway environment config --json

Verify after mutation to confirm the change took effect:

railway environment config --json
railway service list --json

Domains

Use the railway domain command for domain lifecycle work. Avoid raw environment edit JSON patches for normal domain management.

Create domains

railway domain --service <service> --json                  # generate a Railway domain
railway domain example.com --service <service> --json      # add a custom domain
railway domain example.com --service <service> --port 8080 --json

One Railway-provided domain is allowed per service. Multiple custom domains are supported. Custom domain creation returns the DNS records to add at the DNS provider. Add both the routing record and ownership verification record exactly as returned.

Requests to a custom domain can return 404 until Railway verifies the ownership TXT record. DNS can take up to 72 hours to propagate.

Inspect and update domains

railway domain list --service <service> --json
railway domain status example.com --service <service> --json
railway domain update example.com --port 8080 --service <service> --json
railway domain update old-name.up.railway.app --domain new-name --service <service> --json
railway domain certificate retry example.com --service <service> --json

Use status when DNS or certificate issuance is not healthy. Retry certificate issuance only after fixing DNS.

Delete domains

railway domain delete example.com --service <service> --yes --json

Domain deletion is destructive. Confirm the domain and service before running it.

Webhooks

Project webhooks POST a JSON payload to a URL whenever a chosen deployment, monitor, or volume-alert event happens anywhere in the project. They are project-scoped, not per service or environment, and are managed with five MCP tools. There is no railway webhook CLI command. Resolve the project ID from railway status --json or list-projects first.

Tool Purpose
list-webhooks Every webhook in the project: URL, eventTypes, includePreviewEnvironments, the names of its custom headers (headerNames), and the id the other tools take
create-webhook url plus optional eventTypes (defaults to Deployment.failed, Deployment.crashed, Deployment.oom_killed), includePreviewEnvironments (defaults to true), and headers
update-webhook webhookId plus any of url, eventTypes, includePreviewEnvironments, headers. Only the fields passed change; eventTypes and headers each replace the whole set
test-webhook POST a sample event and report the HTTP status. Pass url (and optional headers) to try a new endpoint, or webhookId to send to an existing webhook with its stored URL and headers. Nothing is stored
delete-webhook Remove a webhook by webhookId. Destructive; confirm the URL with the user first

Discord and Slack incoming-webhook URLs are detected and receive a formatted message instead of the raw JSON. Run test-webhook before create-webhook: Railway treats anything outside 2xx/3xx as a failed delivery, and a dead endpoint is paused for 24 hours after repeated failures.

Create webhook for project <project-id>: url https://hooks.example.com/railway, eventTypes Deployment.failed Deployment.crashed, headers { "Authorization": "Bearer <token>" }

Custom headers

A webhook can carry up to 20 custom HTTP headers, sent with every delivery and with test-webhook. Use them for whatever the receiver needs to trust or route the request: an Authorization bearer token, an API key, a shared secret the receiver compares, a routing header. This replaces the old advice of hiding a secret in the URL, which still works but leaks into logs more easily.

  • Values are write-only. They are encrypted at rest and never returned: list-webhooks and every other tool's output carry headerNames only. Keep the value in the user's secret store; Railway cannot show it again.
  • create-webhook and test-webhook take headers as a name-to-value map.
  • update-webhook replaces the whole set. Every header not listed is removed and {} clears them all. A null value keeps the stored value for that name, so a secret survives a change without being resent: { "Authorization": null, "X-Env": "prod" } keeps the token and sets X-Env. A URL-only update carries the stored headers forward automatically.
  • test-webhook with webhookId sends the stored headers; any headers passed alongside override the stored one of the same name and the rest are still sent. Headers are validated before the request goes out, so a bad name gets a clear error instead of a 0 status.
  • Name rules. RFC 9110 token characters, up to 128 characters; values up to 4096 characters with no CR or LF. Host, Content-Type, Content-Length and the hop-by-hop headers (Transfer-Encoding, Connection, Keep-Alive, Upgrade, TE, Trailer, Expect) are refused, as is any name starting with proxy- or x-railway-. Two names differing only by case count as a duplicate. The dashboard form applies the same rules inline and disables Save until a bad row is fixed.

Without MCP, the same fields are on the GraphQL API: headers inside the webhook channel config of notificationRuleCreate / notificationRuleUpdate (a null value keeps the stored one there too), and webhookTest(url, payload, headers: [WebhookHeaderInput!], notificationRuleId). Inspect them with railway api describe.

Networking commands

Private networking

For service-to-service traffic within a project, use private domain references instead of public URLs. This avoids egress and is faster:

BACKEND_URL=http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}
railway private-network status --service <service> --json
railway private-network update api-internal --service <service> --json

When multiple private networks exist, pass --network <name-or-id>. Endpoint updates take the prefix only, without .internal.

TCP proxies

Use TCP proxies to expose non-HTTP ports, such as database or game server ports, to the public internet. Only one TCP proxy is allowed per service instance.

railway tcp-proxy list --service <service> --json
railway tcp-proxy create --service <service> --port 5432 --json
railway tcp-proxy status <proxy-id-or-domain-or-port> --service <service> --json
railway tcp-proxy delete <proxy-id-or-domain-or-port> --service <service> --yes --json

TCP proxy creation updates service networking config. If the proxy does not become active, redeploy the service and check status.

Outbound networking

Use outbound networking commands for Static Outbound IPs and outbound IPv6:

railway outbound-network status --service <service> --json
railway outbound-network static-ip status --service <service> --json
railway outbound-network static-ip enable --service <service> --json
railway outbound-network static-ip disable --service <service> --json
railway outbound-network ipv6 status --service <service> --json
railway outbound-network ipv6 enable --service <service> --json
railway outbound-network ipv6 disable --service <service> --json

Static Outbound IP changes are committed directly but require a redeploy before outbound traffic uses the new assignment. Outbound IPv6 changes are staged as environment config changes; commit staged changes with railway environment edit to trigger the redeploy.

CDN caching

CDN caching is service-scoped and requires an applied public domain.

railway cdn status --service <service> --json
railway cdn enable --service <service> --json
railway cdn update --service <service> --html-caching force --default-ttl 4h --json
railway cdn update --service <service> --no-swr --purge-on-deploy all --json
railway cdn purge html --service <service> --json
railway cdn purge all --service <service> --json
railway cdn disable --service <service> --json

Use the response headers x-cache and age to verify cache behavior. Cache hits do not reach the service, so service logs and server-side metrics can undercount traffic after caching is enabled.

WAF Under Attack Mode

Under Attack Mode is service-scoped, requires an applied public domain, and is intended for active bot floods or DDoS events.

railway waf under-attack status --service <service> --json
railway waf under-attack enable --service <service> --duration 1h --json
railway waf under-attack enable --service <service> --json
railway waf under-attack disable --service <service> --json

While active, browser visitors must pass a check. Non-browser API clients and webhooks may receive 429 responses, so warn the user before enabling it on API-only domains.

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 service list --json
  • Variable change didn't take effect: verify with railway variable list, changes trigger redeploy by default
  • Domain returns errors: run railway domain status, verify the target port, then check HTTP logs
  • DNS propagation delay: custom domains can take up to 72 hours to propagate worldwide
  • Cloudflare proxy issues: align SSL/TLS mode per Railway's domain guidance
  • Private networking failing: run railway private-network status, verify the service is listening on the referenced port, and check network flow logs
  • Outbound allowlist still sees old IPs: redeploy after enabling/disabling Static Outbound IPs
  • IPv6 still disabled: commit the staged environment change and wait for redeploy
  • CDN appears ineffective: check x-cache, age, cache headers, Set-Cookie, Authorization, method, and response size
  • Webhook header rejected: the name is reserved (Host, Content-Type, Content-Length, hop-by-hop), starts with proxy- or x-railway-, duplicates another name ignoring case, or the value has a newline; rename it or drop it, and stay under 20 headers
  • Webhook headers disappeared after an update: update-webhook replaces the whole headers set, so a map without a name removes it; pass null for names to keep, or omit headers entirely
  • WAF breaks API clients: Under Attack Mode blocks non-browser traffic; disable it or scope protection to browser-facing services
  • Multi-region patch ignored: verify region names match the exact identifiers (us-west2, us-east4-eqdc4a, europe-west4-drams3a, asia-southeast1-eqsg3a)

Validated against

Source: SKILL.md on GitHub

1 alert2d5 checks · Risk SAFE
  • Gen Agent Trust Hub2d

    This skill provides a comprehensive toolkit for managing Railway infrastructure, including project deployment, environment configuration, and detailed database performance analysis. It uses official Railway CLI tools and scripts to collect metrics and logs. All external downloads and API calls are directed to official Railway domains, and the scripts include robust security practices like using secure pipes for authentication tokens and requiring interactive confirmation for destructive actions.

  • Socket2d

    1 alert: gptAnomaly

  • Snyk2d

    Risk: LOW · No issues

  • Runlayer6mo

    6/7 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 4db2ee9. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated 2 days ago
What it can do
Runs commands
All 7 allowed tools
Bash(railway:*)Bash(which:*)Bash(command:*)Bash(npm:*)Bash(npx:*)Bash(curl:*)Bash(python3:*)

README badge

README badge for railwayapp/railway-skills