PaaS Deploys skill for Claude Code

Render, Railway or Heroku, configured from the repo. Claude picks the platform that fits the stack, then writes render.yaml, railway.json or a Procfile so services, databases and variables live in the repo. Apps bind to $PORT with real health checks, migrations run once in the release phase and stay backward compatible, database pools fit plan limits, and the free-tier traps such as sleeping instances and expiring databases are called out early.

  • namepaas-deploys
  • size235 lines · ≈2,500 tokens
  • files1
  • licenseMIT
  • updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Deploys and operates apps on Heroku-style platforms - Render, Railway and Heroku - with config as code (render.yaml Blueprints, railway.json, Procfile and app.json), web and worker services, $PORT binding and health checks, managed Postgres and Redis with connection limits, environment groups and variable references, release or pre-deploy phases for migrations, preview environments, zero-downtime deploys and rollback, logs, scaling, and the free-tier and pricing limits that bite. Use when deploying to Render, Railway or Heroku, choosing between them, writing their config files, or debugging failed builds, crashed services, sleeping apps or migration problems.

Try asking

  • Deploy this Django app with a Celery worker and Postgres to Render using a Blueprint
  • Our Railway service crashes with no open ports detected. Fix the deployment
  • Should we move from Heroku to Render or Railway? Compare cost and migration effort

Use it when

  • Deploying a web app, worker or cron job to Render, Railway or Heroku
  • Writing render.yaml, railway.json, Procfile or app.json
  • Builds fail, services crash, or apps sleep on a PaaS
  • Running migrations safely during PaaS deploys

Skip it when

  • You deploy to Fly.io Machines and volumes — use fly-io
  • You want to run everything on your own server — use vps-deployment

How to check the result

The deploy reaches live status with the health check returning 200, the release or pre-deploy migration ran exactly once, total database connections stay under the plan limit, and the previous release is recorded for rollback.

The common mistake it prevents

Rolling back a release after a destructive migration, which restores the old code but not the old schema and breaks the app again.

~/.claude/skills/paas-deploys/SKILL.md

--- name: paas-deploys description: Deploys and operates apps on Heroku-style platforms - Render, Railway and Heroku - with config as code (render.yaml Blueprints,… license: MIT ---

A PaaS deployment is done when the service config lives in the repo, the app binds to $PORT and passes a health check, migrations run once per release before new code takes traffic, secrets live in the platform’s variables, the database plan’s connection limit is respected, and rollback to the previous release is a known one-step action.

Ground rules

  • Config as code: render.yaml, railway.json/railway.toml, or Procfile + app.json. Dashboard-only settings are undocumented and drift.
  • The filesystem is ephemeral on all three (except attached volumes/disks). Uploads go to object storage (see s3-object-storage); sessions go to the database or Redis.
  • Migrations must be backward compatible with the currently running release (expand, deploy, contract; see schema-migrations), because old and new code overlap during zero-downtime deploys.
  • Production deploys, rollbacks, database plan changes, deleting services or databases, and heroku pg:reset-style commands need explicit confirmation. Database deletion is usually irreversible.
  • Free tiers sleep or expire. Never run anything with users or data you need on them.

Choose the platform

NeedRenderRailwayHeroku
Config filerender.yaml Blueprint (whole stack)railway.json/.toml per serviceProcfile, app.json
Migrations before trafficpreDeployCommand (paid instances)deploy.preDeployCommandrelease: process
Zero-downtimeYes with health check (not with an attached disk)Health-checked deploysPreboot feature
Preview environmentsPreview environments from BlueprintsPR environmentsReview apps (pipelines)
Background workers / crontype: worker, type: cronSeparate services, cron schedule per serviceworker: dynos, Scheduler add-on
Private networkingPrivate services on internal hostnames*.railway.internalPrivate Spaces (enterprise)
Pricing shapePer instance type, flat monthlyUsage-based (CPU/RAM/egress)Per dyno type, flat monthly

Default pick: Render for a conventional web + worker + Postgres stack defined in one file; Railway for quick multi-service setups and usage-based billing; Heroku when the team already runs on it or needs its add-on ecosystem. For global placement and VMs, see fly-io; for full control at lowest cost, see vps-deployment.

Workflow

- [ ] 1. Inspect the app: start command, port, build, migrations, background work
- [ ] 2. Write the platform config file
- [ ] 3. Provision database/cache; wire variables by reference
- [ ] 4. Health check, graceful shutdown, release phase
- [ ] 5. Previews and promotion
- [ ] 6. Deploy, verify, record rollback target

1. App contract

  • Listens on 0.0.0.0:$PORT (not 127.0.0.1, not a hardcoded port).
  • /healthz returns 200 quickly without depending on slow downstreams (a separate readiness check can test the database).
  • Handles SIGTERM: stop accepting, finish in-flight requests within ~25 s.
  • Logs to stdout/stderr, one line per event (JSON if possible).
  • Build is deterministic: lockfile committed, runtime version pinned (engines.node, .python-version, .ruby-version).

2. Config files

Render (render.yaml):

services:
  - type: web
    name: api
    runtime: node
    region: frankfurt
    plan: starter
    buildCommand: npm ci && npm run build
    startCommand: node dist/server.js
    preDeployCommand: npm run migrate
    healthCheckPath: /healthz
    envVars:
      - key: NODE_VERSION
        value: "22"
      - key: DATABASE_URL
        fromDatabase: { name: db, property: connectionString }
      - key: REDIS_URL
        fromService: { type: keyvalue, name: cache, property: connectionString }
      - key: SESSION_SECRET
        generateValue: true
      - key: STRIPE_SECRET_KEY
        sync: false                 # prompt in dashboard; never committed
  - type: worker
    name: jobs
    runtime: node
    plan: starter
    buildCommand: npm ci && npm run build
    startCommand: node dist/worker.js
    envVars:
      - fromGroup: shared-settings
  - type: keyvalue
    name: cache
    plan: starter
    ipAllowList: []                 # internal only
databases:
  - name: db
    postgresMajorVersion: "17"
    ipAllowList: []

Check plan names and service types against the current Blueprint spec before applying; Render renamed some (for example Redis to Key Value) over time.

Railway (railway.json, per service):

{
  "$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": 100,
    "restartPolicyType": "ON_FAILURE",
    "restartPolicyMaxRetries": 5
  }
}

Railway variables reference other services: DATABASE_URL=${{Postgres.DATABASE_URL}}; prefer the private URL (*.railway.internal) for service-to-service and database traffic to avoid egress charges. Railpack replaced Nixpacks as the default builder; a Dockerfile in the root takes precedence.

Heroku (Procfile):

web: gunicorn app.wsgi --bind 0.0.0.0:$PORT --workers 3 --timeout 25
worker: celery -A app worker --concurrency 4
release: python manage.py migrate --noinput
heroku features:enable preboot -a acme-api       # zero-downtime dyno swap
heroku config:set DJANGO_SETTINGS_MODULE=app.settings.prod -a acme-api
heroku ps:scale web=2 worker=1 -a acme-api

3. Databases and connections

  • Managed Postgres plans cap connections. Size pools so instances x workers x pool_size stays under ~80% of the limit; add PgBouncer (platform-provided or sidecar) when web concurrency grows.
  • Use internal connection strings inside the platform; external URLs only for admin tools, with IP allowlists.
  • Backups: confirm plan includes daily backups/PITR; test a restore into a scratch database before you need it (see backup-restore).
  • Free Postgres on Render expires after 30 days (then a short grace period before deletion); never keep real data there.

4. Release phase and health

PlatformMigration hookFailure behavior
RenderpreDeployCommandDeploy aborts, old instance keeps serving
RailwaypreDeployCommandDeploy fails, previous deployment stays live
Herokurelease: processRelease fails, dynos not restarted with new slug

Keep hooks idempotent and fast (< a few minutes). Long data backfills run as one-off jobs (render jobs, railway run, heroku run) after the deploy, not in the hook.

5. Previews and promotion

  • Render: enable preview environments in the Blueprint (previews.generation) so each PR gets the full stack; set preview plans small and expiration short.
  • Railway: PR environments clone services and variables; seed databases explicitly.
  • Heroku: pipeline with review apps -> staging -> production; promote the exact slug (heroku pipelines:promote -a acme-staging), which skips rebuilding.

6. Deploy, verify, roll back

# Render: deploys on push (auto-deploy) or via deploy hook / API; logs in dashboard or CLI
render deploys list <service-id>            # CLI commands vary by version; check render --help
# Railway
railway up --service api                    # deploy current directory
railway logs --service api
railway status
# Heroku
git push heroku main
heroku releases -a acme-api
heroku logs --tail -a acme-api
heroku releases:rollback v142 -a acme-api   # confirm first; does not roll back DB migrations

Rollback restores code and config, never the database schema. That is why migrations must be backward compatible.

Debugging

SymptomCauseFix
“No open ports detected” / H10 crash / health check timeoutApp binds to localhost or a fixed portBind 0.0.0.0:$PORT
Heroku H12 request timeoutRequest > 30 s at the routerMove work to a worker; stream responses
Heroku R14/R15 memoryExceeds dyno memoryFewer workers, fix leak, bigger dyno
Works then data vanishesWriting to ephemeral filesystemObject storage or attached disk/volume
too many connectionsPool sizes x instances > plan limitShrink pools, add PgBouncer, upgrade plan
First request takes ~30-60 sFree/sleeping instance cold startPaid always-on instance
Build fails only on platformDifferent runtime version, missing build var, devDependencies prunedPin versions; set build-time vars; adjust build command
Deploy succeeded but old code servedHealth check path missing so platform kept old instance, or CDN cacheCheck deploy events; purge CDN

Gotchas

  • Render services with an attached persistent disk cannot scale horizontally and deploy with brief downtime; prefer managed databases and object storage.
  • Heroku dynos restart roughly daily (cycling); anything held in memory or on disk is lost. Schedulers that rely on uptime miss runs.
  • Railway’s usage-based billing grows with idle memory; set resource limits per service and remove abandoned PR environments.
  • Variable changes trigger redeploys on most platforms; batch them.
  • Platform-managed TLS and custom domains need DNS pointed correctly (CNAME or ALIAS/flattening at the apex; see dns-tls-certificates).
  • Outbound IPs are shared and change; databases elsewhere cannot allowlist them without a static egress add-on.

Output

**Platform:** Render (render.yaml Blueprint, Frankfurt)
**Services:** api (web, starter, /healthz, preDeploy migrate), jobs (worker), cache (key value), db (Postgres 17)
**Variables:** DATABASE_URL/REDIS_URL by reference; secrets sync:false; shared group for workers
**Connections:** 2 api instances x pool 10 + worker pool 5 = 25 of 97 allowed
**Previews:** per-PR environments, 3-day expiry
**Verified:** deploy live, health 200, migration ran once, rollback target = deploy dep-cq1... (code only)

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc