| name | deploy |
| description | Deploy Rails applications to Railway. Handles first-time setup and re-deploys idempotently using Railway CLI. Trigger on: "deploy", "deploy to railway", "railway deploy", "发布", "部署", "上线". |
Deploy Rails App to Railway
Deploy the current Rails project to Railway using the Railway CLI. Works for both first-time deploys and re-deploys.
Prerequisites Check
Before starting, verify:
railway --version
railway whoami
If not logged in, instruct the user:
Please run: railway login
Then retry deployment.
Step 0: Prepare for Linux Build
Railway runs on Linux. Ensure Gemfile.lock includes the linux platform:
bundle lock --add-platform x86_64-linux
If the project uses a Dockerfile builder (check railway.toml for builder = "DOCKERFILE"), no Procfile is needed — skip creating one.
Only create a Procfile if there is no Dockerfile:
web: bundle exec puma -C config/puma.rb
Step 1: Check Link Status → Deploy Immediately if Already Linked
First: check if already linked:
railway status 2>&1
If output contains Project: → project is already linked.
Skip Steps 2–5 entirely and jump to Step 6 (Deploy).
If output contains "not linked" or an error → not linked yet.
Try linking to an existing project first — list available projects:
railway list 2>&1 | grep -i "<app-name>"
If a matching project is found, link it:
railway link --project <project-name> --service <service-name> 2>&1
Only if no existing project is found, init a new one:
railway init -n <app-name>
The app-name should match the current directory name (e.g., my-rails-app).
⚠️ NEVER run railway init when already linked or when an existing project exists.
It silently creates a brand-new Railway project. If this happens by mistake:
- Find the correct project name from
railway list
- Re-link:
railway link --project <project-name> --service <service-name>
Step 2: Set Environment Variables
Set required Rails production variables. Use --skip-deploys to avoid triggering premature deploys:
SECRET_KEY_BASE=$(bundle exec rails secret)
railway variable set SECRET_KEY_BASE=$SECRET_KEY_BASE --skip-deploys
railway variable set RAILS_ENV=production --skip-deploys
railway variable set RAILS_LOG_TO_STDOUT=true --skip-deploys
railway variable set RAILS_SERVE_STATIC_FILES=true --skip-deploys
If the project uses any other env vars (check .env.example or config/application.yml.example if they exist), prompt the user to provide values and set them too.
If the project uses config/application.yml (Figaro gem), read it and set all values as Railway variables:
ruby -ryaml -e "
data = YAML.safe_load(File.read('config/application.yml')) || {}
data.each { |k, v| puts %(railway variable set #{k}=#{v} --skip-deploys) unless v.to_s.empty? }
" | bash
Step 3: Ensure PostgreSQL Service (Idempotent)
Check if Postgres already exists:
railway status --json
Parse the JSON output. If a service with type postgres or name containing postgres/Postgres is already found, skip with: ✅ PostgreSQL already provisioned
⚠️ IMPORTANT: railway add --database postgres has a known CLI bug that always returns Unauthorized.
Do NOT attempt to run this command. Instead, instruct the user to add PostgreSQL manually via the Railway Web UI:
- Open your Railway project:
https://railway.com/project/<project-id>
(Get the project ID from the Railway dashboard or cat .railway/config.json)
- Click "+ New" → "Database" → "PostgreSQL"
- Wait for the database to provision
- Come back and continue
After Postgres is provisioned, set the DATABASE_URL variable:
railway variable set DATABASE_URL='${{Postgres.DATABASE_URL}}' --skip-deploys
Step 4: Get Domain (Idempotent)
Check if a domain is already set:
railway domain --json
If no domain exists yet:
railway domain
Capture and display the domain URL to the user. Also set it as PUBLIC_HOST:
railway variable set PUBLIC_HOST=<domain-without-https> --skip-deploys
Step 5: Configure Storage Bucket (if needed)
Check if the project uses S3-compatible storage by reading config/storage.yml. If it contains an amazon or s3 service section, storage bucket configuration is required.
⚠️ Storage bucket requires Railway Hobby plan ($5/month minimum). Confirm with the user before proceeding.
Check if bucket env vars are already set:
railway variables --json | grep STORAGE_BUCKET
If not set, create a bucket and configure the variables:
railway bucket create <app-name>-storage --region iad --json
railway bucket credentials --bucket <app-name>-storage --json
The credentials JSON will contain: accessKeyId, secretAccessKey, region, endpoint, bucketName.
Set them as environment variables:
railway variables set \
STORAGE_BUCKET_ACCESS_KEY_ID=<accessKeyId> \
STORAGE_BUCKET_SECRET_ACCESS_KEY=<secretAccessKey> \
STORAGE_BUCKET_REGION=<region> \
STORAGE_BUCKET_NAME=<bucketName> \
STORAGE_BUCKET_ENDPOINT=<endpoint> \
--skip-deploys
⚠️ Missing these variables will cause a hard crash at boot (Aws::Errors::MissingRegionError) because the AWS SDK initializes at startup, not lazily.
If the project does not use S3 storage, skip this step entirely.
Step 6: Deploy
Upload and deploy the project:
railway up --detach
Show the user the deployment is in progress and they can monitor it with:
railway logs
No manual migration needed. The bin/docker-entrypoint script runs rails db:prepare automatically on container startup. Just wait for the deployment to complete.
Step 7: Verify Deployment
After deployment completes (wait ~30 seconds), verify the app is running:
curl -s -o /dev/null -w "%{http_code}" https://<domain>/
If it returns 200, deployment is successful. If not, check logs:
railway logs --tail 50
Step 8: Done
Print a summary:
✅ Deployment complete!
🌐 Platform URL: https://<domain>
📋 Monitor: railway logs
🔄 Re-deploy: just run deploy again
Notes
- Idempotency: Running this skill multiple times is safe. Each step checks current state before acting.
- Link detection: Use
railway status to check if already linked — it's more reliable than checking .railway/config.json (works across machines and fresh clones).
- Re-deploy: On subsequent runs, Steps 1–5 are all skipped or no-ops. Only Step 6 (upload) actually runs.
- Secret key: Only set
SECRET_KEY_BASE if not already set (check with railway variable list).
- Database migrations: Handled automatically by
bin/docker-entrypoint via rails db:prepare — never run railway run bundle exec rails db:migrate as Railway's internal DB IP is not accessible from local machine.
- PostgreSQL CLI bug:
railway add --database postgres always fails with Unauthorized — always use the Web UI instead.