Railway and Render Deploys skill for Codex
Your whole PaaS stack declared in the repo. Codex declares web services, workers, cron jobs and managed Postgres or Redis in render.yaml or railway.json instead of dashboard clicks. It binds apps to the injected PORT, wires services with variable references over the private network, runs migrations in the pre-deploy step so a failure never ships, and flags disks and sleeping free instances before they cause outages.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- paas-deploys Installs to ~/.agents/skills/paas-deploys/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install paas-deploys The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project paas-deploys Installs to .agents/skills/paas-deploys/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/paas-deploys.zipunzip -o paas-deploys.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) paas-deploys PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $paas-deploys, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Ships web services, workers, cron jobs and managed Postgres or Redis on Railway and Render - render.yaml Blueprints, railway.json config as code, PORT binding, health checks, pre-deploy migrations, variable references between services, private networking, persistent disks and free-tier limits. Use when deploying an app to Railway or Render, moving off Heroku, wiring a database URL between services, a deploy fails health checks, a free service sleeps, or you want the infrastructure checked into git instead of clicked in a dashboard.
Use it when
- Deploying a web app, worker or cron job to Railway or Render
- Migrating off Heroku to a modern PaaS
- Connecting services and databases with private URLs and variable references
- Turning a dashboard-configured setup into config checked into git
Use something else when
- You need multi-region VMs with volumes: use fly-io
- You want to run everything on your own server: use vps-deploys
How to check the result
A fresh preview or staging environment deploys from the committed config alone, the health check passes, and the database accepts only private connections.
The mistake it prevents
Running migrations in the start command, so every instance races to migrate and a failed migration crash-loops the service.
A good PaaS setup is reproducible from the repo: services, databases and variables are
declared in render.yaml or railway.json, migrations run in a pre-deploy step rather
than at boot, the app binds to the injected PORT, and services talk over the private
network instead of public URLs.
Inspect first
- Read
AGENTS.md,package.jsonor equivalent, theDockerfileif any, and existingrender.yaml,railway.jsonorrailway.toml,Procfile. - Find how the app reads its port and database URL. Both platforms inject
PORT; the app must listen on0.0.0.0(Railway private networking may need::). - List what the app needs: web service, background worker, cron, Postgres, Redis, disk.
- Platform CLIs and dashboards need network and login. In an offline sandbox, write the config files and hand deploy commands to the user.
Choosing between them
| Factor | Railway | Render |
|---|---|---|
| Config as code | railway.json / railway.toml per service | One render.yaml Blueprint for the whole stack |
| Builds | Railpack auto-detect or Dockerfile | Native runtimes (node, python, ruby, go, rust, elixir) or Docker |
| Pricing model | Usage-based (CPU, RAM, egress) | Per-instance plans |
| Previews | PR environments | Preview environments from the Blueprint |
| Good fit | Many small services, quick experiments, variable references | Stable stacks wanting declarative Blueprints and predictable plan costs |
Either is fine for a typical web plus worker plus Postgres app. Pick the one the team already uses; migration between them is mostly config translation.
Render Blueprint
# render.yaml
services:
- type: web
name: api
runtime: node
plan: starter
region: oregon
buildCommand: npm ci && npm run build
startCommand: node dist/server.js
preDeployCommand: npm run migrate
healthCheckPath: /healthz
envVars:
- key: DATABASE_URL
fromDatabase: { name: main-db, property: connectionString }
- key: REDIS_URL
fromService: { type: keyvalue, name: cache, property: connectionString }
- key: SESSION_SECRET
generateValue: true
- key: STRIPE_SECRET_KEY
sync: false # entered in the dashboard, never in git
- fromGroup: shared-settings
- type: worker
name: jobs
runtime: node
plan: starter
buildCommand: npm ci && npm run build
startCommand: node dist/worker.js
envVars:
- key: DATABASE_URL
fromDatabase: { name: main-db, property: connectionString }
- type: cron
name: nightly-report
runtime: node
schedule: "0 4 * * *"
buildCommand: npm ci && npm run build
startCommand: node dist/report.js
- type: keyvalue
name: cache
plan: starter
ipAllowList: [] # private network only
databases:
- name: main-db
plan: basic-256mb
postgresMajorVersion: "17"
ipAllowList: []
envVarGroups:
- name: shared-settings
envVars:
- key: LOG_LEVEL
value: info
Plan names change over time; confirm current values in the Render dashboard or docs before committing. Apply a Blueprint from the dashboard (New, Blueprint) the first time; later pushes sync it.
Railway config as code
{
"$schema": "https://railway.com/railway.schema.json",
"build": { "builder": "RAILPACK", "buildCommand": "npm run build" },
"deploy": {
"startCommand": "node dist/server.js",
"preDeployCommand": ["npm run migrate"],
"healthcheckPath": "/healthz",
"healthcheckTimeout": 120,
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 5
}
}
Each service in a Railway project has its own config file path; set it in the service settings when a monorepo holds several.
railway login
railway link # choose project and environment
railway add --database postgres # adds a Postgres service
railway variables --set "LOG_LEVEL=info"
railway run npm run migrate # runs locally with the service's variables
railway up # deploy current directory (confirm for production)
railway logs
Variable references wire services together without copying secrets:
DATABASE_URL = ${{Postgres.DATABASE_URL}} # private connection string
API_INTERNAL = http://${{api.RAILWAY_PRIVATE_DOMAIN}}:${{api.PORT}}
SHARED_FLAG = ${{shared.SHARED_FLAG}}
Use the private URL for service-to-service and app-to-database traffic; the public URL
(DATABASE_PUBLIC_URL) costs egress and is exposed to the internet.
Migrations and releases
- Run migrations in the pre-deploy step. If it fails, the new version is not released and the old one keeps serving.
- Never run migrations in the start command of a multi-instance service: every instance races to migrate.
- Make migrations backward compatible with the running version (expand, migrate code, contract later), because old and new instances overlap during the rollout.
Disks and state
Attaching a persistent disk on either platform pins the service to one instance and usually means a short downtime on each deploy, since the disk cannot attach to two instances. Prefer managed Postgres, Redis and object storage; use disks only for things like a self-hosted CMS that insists on local files, and back them up.
Free tiers and sleeping
- Render free web services spin down after about 15 minutes idle and take tens of seconds to wake; free Postgres instances expire after a fixed period. Fine for demos, never for production or anything receiving webhooks.
- Railway trial and hobby credits run out; a service stops when they do. Set usage alerts and limits.
Debug table
| Symptom | Check |
|---|---|
| Deploy fails health check | Listening on PORT and 0.0.0.0, health path returns 200 without auth, timeout long enough for boot |
ECONNREFUSED to database | Using a public URL blocked by allow list, or private hostname from outside the project |
| Railway private DNS fails | App bound to IPv4 only; listen on :: |
| Works locally, crashes on boot | Missing variable; compare railway variables or the Render env tab with .env.example |
| Webhooks time out | Free instance asleep; upgrade plan or move the endpoint |
Verification
- Config file validates (Render Blueprint preview, Railway schema in the editor).
- A preview or staging environment deploys from the config with no dashboard edits.
- Health endpoint returns 200; logs show the pre-deploy migration ran once.
- Database is reachable only privately;
ipAllowListor public networking reviewed. - Production deploys, database deletions and plan changes only after explicit user confirmation.
Gotchas
generateValue: truecreates the secret once; changing the Blueprint later does not rotate it.- Deleting a Render database or a Railway Postgres service deletes its data; confirm a fresh backup and get explicit approval first.
- Build-time and runtime variables differ on Render Docker services; secrets needed during
docker buildmust be passed as build args or secret files deliberately. - Cron services run in their own container; they do not share memory or disk with the web service.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Railway and Render Deploys"
short_description: "Your whole PaaS stack declared in the repo"
default_prompt: "$paas-deploys Write a render.yaml for our API, worker, cron job and Postgres database" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.