name: render-migrate-from-heroku description: "Migrate from Heroku to Render by reading local project files and generating equivalent Render services. Triggers: any mention of migrating from Heroku, moving off Heroku, Heroku to Render migration, or switching from Heroku. Reads Procfile, dependency files, and app config from the local repo. Optionally uses Heroku MCP to enrich with live config vars, add-on details, and dyno sizes. Uses Render MCP or Blueprint YAML to create services." license: MIT compatibility: Requires the Render MCP server. Heroku MCP server is optional (enhances config var and add-on discovery). metadata: author: Render version: "1.2.0" category: migration
Heroku to Render Migration
Migrate from Heroku to Render by reading local project files first, then optionally enriching with live Heroku data via MCP.
Prerequisites Check
Before starting, verify what's available:
- Local project files (required) — confirm the current directory contains a Heroku app (look for
Procfile,app.json,package.json,requirements.txt,Gemfile,go.mod, or similar) - Render MCP (required) — confirm access to
list_servicestool - Heroku MCP (optional) — check if
list_appstool is available
If Render MCP is missing, instruct the user to configure it. If Heroku MCP is missing, note that config var values and add-on plan details will need to be provided manually. See the MCP setup guide.
Migration Workflow
Execute steps in order. Present findings to the user and get confirmation before creating any resources.
Step 1: Inventory Heroku App
Gather app details from local files first, then supplement with Heroku MCP if available.
1a. Read local project files (always)
Read these files from the repo to determine runtime, commands, and dependencies:
| File | What it tells you |
|---|---|
Procfile |
Process types and start commands (web, worker, clock, release) |
package.json |
Node.js runtime, build scripts, framework deps (Next.js, React, etc.) |
requirements.txt / Pipfile / pyproject.toml |
Python runtime, dependencies (Django, Flask, etc.) |
Gemfile |
Ruby runtime, dependencies (Rails, Sidekiq, etc.) |
go.mod |
Go runtime |
Cargo.toml |
Rust runtime |
app.json |
Declared add-ons, env var descriptions, buildpacks |
runtime.txt |
Pinned runtime version |
static.json |
Static site indicator |
yarn.lock / pnpm-lock.yaml |
Package manager (affects build command) |
From these files, determine:
- Runtime — from dependency files (see the buildpack mapping)
- Build command — from package manager and framework (see the buildpack mapping)
- Start commands — from
Procfileentries - Process types — from
Procfile(web, worker, clock, release) - Add-ons needed — from
app.jsonaddonsfield, or infer from dependency files (e.g.,pginpackage.jsonsuggests Postgres,redissuggests Key Value) - Static site? — from
static.json, SPA framework deps, or static buildpack inapp.json
1b. Enrich with Heroku MCP (if available)
If the Heroku MCP server is connected, call these tools to fill in details that aren't in the repo:
list_apps— let user select which app to migrate (confirms app name)get_app_info— capture: region, stack, buildpacks, config var nameslist_addons— identify exact add-on plans (Postgres tier, Redis tier)ps_list— capture dyno types, sizes, and counts (maps to Render plan tiers)
If Heroku MCP is not available, ask the user to provide:
- App region (
usoreu) - List of add-ons and their plans (or run
heroku addons -a <app>in terminal) - Config var names (or run
heroku config -a <app> --shelland paste output)
Present summary
App: [name] | Region: [region] | Runtime: [node/python/ruby/etc]
Source: [local files | local files + Heroku MCP]
Build command: [inferred from buildpack/deps]
Processes:
web: [command from Procfile] → Render web service
worker: [command] → Render background worker (Blueprint only)
clock: [command] → Render cron job
release: [command] → Append to build command
Add-ons: Heroku Postgres (standard-0), Heroku Data for Redis (mini)
Config vars: 14 total (list names, not values)
Step 2: Pre-Flight Check
Before creating anything, validate the migration plan and present it to the user. Check for:
- Runtime supported? If buildpack maps to
docker, warn user they need a Dockerfile - Worker dynos? Flag these — can be defined in a Blueprint (
type: worker, minimum planstarter), but cannot be created via MCP tools directly - Release phase? If Procfile has
release:, suggest appending to build command - Static site? Check for static buildpack,
static.json, or SPA framework deps — usecreate_static_siteinstead ofcreate_web_service. See detection rules in the buildpack mapping. - Third-party add-ons? List any add-ons without direct Render equivalents (e.g., Papertrail, SendGrid) — user needs to find alternatives and update env vars
- Multiple process types? If Procfile has >1 entry, each becomes a separate Render service (except
release:) - Repo URL available? Ask user for their GitHub/GitLab repo URL (required for service creation)
- Database size? If Postgres is Premium/large tier, recommend contacting Render support for assisted migration
Present the full plan as a table:
MIGRATION PLAN — [app-name]
─────────────────────────────────
CREATE (include only items that apply):
✅ Web service ([runtime], starter) — startCommand: [cmd]
✅ Background worker ([runtime], starter) — startCommand: [cmd]
✅ Cron job (starter) — schedule: [cron expr] — command: [cmd]
✅ Postgres (basic-1gb)
✅ Key Value (starter)
METHOD: [Blueprint | MCP Direct Creation]
MANUAL STEPS REQUIRED:
⚠️ Custom domain: [domain] — configure after deploy
⚠️ Replace add-on: [name] → find alternative
ENV VARS: [N] to migrate, [M] filtered out
DATABASE: [size] — pg_dump/pg_restore required
─────────────────────────────────
Proceed? (y/n)
Wait for user confirmation before creating any resources.
Choose Creation Method
After the user approves the pre-flight plan, select the creation method:
Use MCP Direct Creation when ALL are true:
- Single web or static site service only
- No background workers or cron jobs
- No databases or Key Value stores needed
Use Blueprint (recommended for most migrations) when ANY are true:
- Multiple process types (web + worker, web + cron, etc.)
- Databases or Key Value stores needed
- Background workers in the Procfile
- User prefers Infrastructure-as-Code configuration
Most Heroku apps include at least a database, so Blueprint is the default path. If unsure, use Blueprint.
Step 3A: Generate Blueprint (Multi-Service)
Generate a render.yaml file using the projects/environments pattern to group all migrated resources in a single Render project. See the Blueprint example for a complete example and the Blueprint YAML spec for the full reference.
Use the correct default plan for each service type. Since Heroku has no free tier, default to the cheapest paid Render plan (upgrade if the Heroku plan maps higher):
| Service type | Default plan |
|---|---|
Web service (type: web) |
starter |
Static site (runtime: static) |
starter |
Background worker (type: worker) |
starter |
Cron job (type: cron) |
starter |
Key Value (type: keyvalue) |
starter |
| Postgres (database) | basic-1gb |
Blueprint structure:
projects:
- name: <heroku-app-name>
environments:
- name: production
services:
- type: web
name: <app>-web
runtime: <mapped-runtime>
plan: starter
buildCommand: <build-cmd>
startCommand: <web-cmd>
envVars:
- key: DATABASE_URL
fromDatabase:
name: <app>-db
property: connectionString
- key: REDIS_URL
fromService:
type: keyvalue
name: <app>-cache
property: connectionString
- key: NON_SECRET_VAR
value: <value>
- key: SECRET_VAR
sync: false
# Include only if worker dyno exists
- type: worker
name: <app>-worker
runtime: <mapped-runtime>
plan: starter
buildCommand: <build-cmd>
startCommand: <worker-cmd>
envVars:
- key: NON_SECRET_VAR
value: <value>
- key: SECRET_VAR
sync: false
# Include only if scheduler/clock exists
- type: cron
name: <app>-cron
runtime: <mapped-runtime>
plan: starter
schedule: "<cron-expression>"
buildCommand: <build-cmd>
startCommand: <cron-cmd>
envVars:
- key: NON_SECRET_VAR
value: <value>
- key: SECRET_VAR
sync: false
# Include only if Redis add-on exists
- type: keyvalue
name: <app>-cache
plan: starter
ipAllowList:
- source: 0.0.0.0/0
description: everywhere
databases:
# Include only if Postgres add-on exists
- name: <app>-db
plan: basic-1gb
Key rules:
- Use
fromDatabaseforDATABASE_URL— never hardcode connection strings - Use
fromServicewithtype: keyvalueandproperty: connectionStringforREDIS_URL - Define env vars directly on each service (do not use
envVarGroups) - Mark secrets with
sync: false(user fills these in the Dashboard during Blueprint apply) - Map region from Heroku using the service mapping
- Only include service/database blocks that the Heroku app actually uses
After generating render.yaml:
- Write the file to the repo root
- Validate the Blueprint — run
render blueprints validate render.yamland show the output to the user. If validation fails, fix the errors in the YAML and re-validate until it passes. Do not proceed until validation succeeds. - Instruct user to commit and push:
git add render.yaml && git commit -m "Add Render migration Blueprint" && git push - Get the repo URL by running
git remote get-url origin. If the URL is SSH format (e.g.,git@github.com:user/repo.git), convert it to HTTPS (https://github.com/user/repo). Then construct the deeplink:https://dashboard.render.com/blueprint/new?repo=<HTTPS_REPO_URL> - Present the actual working deeplink to the user — never show a placeholder URL. Guide user to open it, fill in
sync: falsesecrets, and click Apply
Step 3B: MCP Direct Creation (Single-Service)
For single-service migrations without databases, create via MCP tools:
- Web service —
create_web_servicewith:runtime: from the buildpack mappingbuildCommand: from the buildpack mappingstartCommand: from Procfileweb:entryrepo: user-provided GitHub/GitLab URLregion: mapped from Heroku regionplan:starter(or mapped from dyno size, see the service mapping)
- Static site —
create_static_siteif detected (instead of web service)
Present the creation result (service URL, ID) when complete.
Step 4: Migrate Environment Variables
Gather config vars
Use the first available source:
- Heroku MCP (preferred) — config vars from
get_app_inforesults (Step 1b) - User-provided — ask the user to paste output of
heroku config -a <app> --shell app.json— var names and descriptions (no values, but useful forsync: falseentries)
Filter and categorize
Remove auto-generated and Heroku-specific vars (see the full filter list in the service mapping):
DATABASE_URL,REDIS_URL,REDIS_TLS_URL(Render generates these)HEROKU_*vars (e.g.,HEROKU_APP_NAME,HEROKU_SLUG_COMMIT)- Add-on connection strings (
PAPERTRAIL_*,SENDGRID_*, etc.)
Present filtered list to user — do not write without confirmation.
Apply vars
Blueprint path (Step 3A): Env vars are already embedded in the render.yaml on each service (non-secret values inline, secrets marked sync: false for the user to fill in during Blueprint apply). No separate MCP call is needed — skip to Step 5.
MCP path (Step 3B): Call Render update_environment_variables with confirmed vars (supports bulk set, merges by default).
Step 5: Database Migration (Commands Only)
Neither MCP server supports pg_dump/pg_restore. Generate commands using connection info from both sides:
- Source connection string — use the first available:
- Heroku MCP
pg_info(if available) - Ask the user to run
heroku pg:credentials:url -a <app>and paste the result
- Heroku MCP
- Call Render
get_postgresfor destination connection info - Present commands with actual connection strings substituted:
# Put Heroku in maintenance mode (can use maintenance_on via Heroku MCP)
# Dump from Heroku
pg_dump -Fc --no-acl --no-owner -d <HEROKU_DB_URL> > heroku_dump.sql
# Restore to Render
pg_restore --clean --no-acl --no-owner -d <RENDER_EXTERNAL_DB_URL> heroku_dump.sql
Remind user: schedule maintenance window, databases >50GB should contact Render support.
Step 6: Verify Migration
After user confirms database migration is complete:
list_deploys/get_deploy— check deploy statuslist_logs— pull recent logs, check for errorsget_metrics— CPU, memory, HTTP latencyquery_render_postgres— read-only query to confirm data (e.g., row count on a key table)
Present health summary.
Step 7: DNS Cutover (Manual)
Instruct user to:
- Add CNAME pointing domain to
[service-name].onrender.com - Remove/update old Heroku DNS entries
- Wait for propagation
Rollback Plan
If the migration fails at any point:
- Services created but not working: Services can be deleted from the Render dashboard (MCP server intentionally does not support deletion). Heroku app is untouched until maintenance mode is enabled.
- Env vars wrong: Call
update_environment_variableswithreplace: trueto overwrite, or fix individual vars. - Database migration failed: Render Postgres can be deleted and recreated. Heroku database is read-only during dump (no data loss). If
maintenance_offis called on Heroku, the original app is fully operational again. - DNS already changed: Revert CNAME to Heroku and disable maintenance mode on Heroku.
Key principle: Heroku stays fully functional until the user explicitly cuts over DNS. The migration is additive until that final step.
Error Handling
- Service creation fails: show error, suggest fixes (invalid plan, bad repo URL)
- Env var migration partially fails: show which succeeded/failed
- Heroku auth errors: instruct
heroku loginor checkHEROKU_API_KEY - Render auth errors: check Render API key in MCP config