# Moving from Heroku to Cloud Run

> What each part of a Heroku app becomes on Google Cloud, the order of the move, the commands for each part, what it costs on both sides, what breaks, and the same map for Railway, Render and Fly.

Published 2026-10-10 · Updated 2026-10-13 · [By Exodus team](https://exodus.elim.io/about) · From Heroku
<https://exodus.elim.io/blog/heroku-to-cloud-run>

Moving from Heroku to Cloud Run turns the web dyno into a Cloud Run service, workers into Cloud Tasks, Heroku Scheduler into Cloud Scheduler, the release phase into a migrate job, Heroku Postgres into Cloud SQL, and config vars into Secret Manager. Users stay on Heroku for 4 of 5 steps. A small app with Postgres costs $19.00 a month on Basic dynos, about $11.09 on Cloud Run.

## In short

- Heroku has run a sustaining engineering model since February 6, 2026: no new features, and credit-card customers keep their prices and service.
- A web dyno, a worker and Essential-0 Postgres cost $19.00 a month on Basic dynos and $55.00 on Standard-1X; on Cloud Run with Cloud SQL, $11.09 at 2,000 visits a day.
- Cloud SQL's smallest instance with 10 GiB is $9.37 a month, more than Essential-0's $5, so the savings come from the dynos.
- Heroku Scheduler jobs become Cloud Scheduler jobs, 3 of them free per billing account.
- Cloud Run domain mappings are in Preview in 10 regions; lower the DNS TTL to 300 seconds a day before the switch.

Prices are Heroku's and Google's list prices, read on October 12, 2026.

## Why teams move now

Since February 6, 2026, Heroku keeps running and gets security and stability work, but no new features. New customers can no longer sign an Enterprise contract, and existing contracts are honored. Nothing forces you to leave tomorrow, but many teams took it as the moment to plan where their app goes next. An outage in June 2025, up to 24 hours for many customers, with the status page down too, had already made some of them look.

Cloud Run is a close fit for a Heroku app, because both run a stateless web process from your code and both scale it for you. What changes is billing: Heroku bills a dyno for every hour it exists, and Cloud Run, by default, only for the seconds it handles requests. That one rule is why a few habits of a Heroku app have to change before the move, and why the bill usually falls. If you have never used Google Cloud, [what Cloud Run is](https://exodus.elim.io/blog/what-cloud-run-is) explains the basics first.

## What each part becomes

![Two columns of boxes with arrows. Web dyno becomes a Cloud Run service. Worker dyno becomes Cloud Tasks and a handler, per job. Heroku Scheduler becomes Cloud Scheduler, which calls. The release line in the Procfile becomes a Cloud Run job named migrate. Heroku Postgres becomes Cloud SQL for PostgreSQL through a dump. Heroku Key-Value Store often becomes nothing. Config vars become Secret Manager and environment variables through a pipe. The herokuapp.com address becomes a run.app address, then your domain](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-076cdea6bc1784cca53d3b69f23ade1c9bdce918969d1460d34429094e04c7bb-680x436-svg/bytes "A Heroku app, part by part, on Google Cloud. Only the web dyno becomes something always present; the rest is called when there is work.")

- **Web dyno** becomes a Cloud Run **service**, built from the same code. It listens on the port in `PORT`, as on Heroku.
- **Worker dyno** (Sidekiq, Celery, BullMQ, RQ) becomes **Cloud Tasks** calling a handler in your service, one HTTP request per job. A worker that must keep a loop running, such as a stream consumer, goes to a **worker pool**, generally available since April 2026, which bills all the time it runs.
- **Heroku Scheduler** becomes **Cloud Scheduler**, which calls an endpoint of the service for short work or starts a Cloud Run **job** for long work. A clock process (a `clock:` line in the Procfile) goes the same way.
- **The release phase** (`release:` in the Procfile, usually migrations) becomes a **migrate job** that runs before the new revision takes traffic.
- **Heroku Postgres** becomes **Cloud SQL for PostgreSQL**, or a serverless Postgres outside Google when the database is small.
- **Heroku Key-Value Store**, the Redis add-on, often becomes nothing: sessions move into signed cookies and the queue into Cloud Tasks. A cache shared between instances is the case where a Redis stays.
- **Config vars** become **Secret Manager** secrets for the secret ones and plain environment variables for the rest.
- **The `herokuapp.com` address** becomes the service's `run.app` address, and later your own domain in front of it.
- `heroku run bash` becomes a one-off Cloud Run job with your command. Google announced SSH into a running instance in April 2026 (`gcloud beta run services ssh`), but it is in preview for selected customers.
- **Review apps** become revisions deployed with a tag and no traffic, each at its own address.

## What breaks first

A web dyno runs all the time with its CPU on. A Cloud Run instance gets CPU only while a request is in flight, its disk is memory, and it may stop whenever traffic falls. Four habits of a Heroku app break because of that: timers inside the process, work done after the response is sent, files written to local disk, and sessions or queues held in memory. [The patterns that break on Cloud Run](https://exodus.elim.io/blog/patterns-that-break-on-cloud-run) goes through each with its replacement.

The Exodus CLI reads a Heroku repository's `Procfile`, `app.json` and code and lists them. Here it is on a small Express app with a web, a worker and a release line, Postgres, the Key-Value Store and Scheduler:

![Terminal output of exodus audit on a sample Heroku app: platform Heroku from app.json; four processes, web running node server.js, worker running node worker.js, release running node migrate.js, and a scheduler from app.json; data postgres, redis and files on disk at server.js line 9; then exodus report says you can move sample-app to Cloud Run after 4 changes](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-f2f7e119db4739e232fb9bae70d0051610658eda098f51c06ac385e99fcb44b4-680x308-svg/bytes "exodus 0.1.3 on a sample Heroku app, Oct 12, 2026. Each process and add-on becomes a line of the report and a task of the plan.")

Its four changes were the uploads to disk, the sessions in memory, a `node-cron` timer and the BullMQ worker. It did not flag the signup handler, which sends a welcome email after the response; that is the habit to look for by hand.

## The order of the move

![Five numbered steps with where the users are during each. 1, find what breaks: timers, work after the response, disk, sessions; users on Heroku. 2, change the code on Heroku, each change shipped and tested there; users on Heroku. 3, stand up the new app with its run.app address, Cloud SQL, secrets and schedules; users on Heroku. 4, rehearse the copy and time it, because that is the freeze on the day; users on Heroku. 5, cut over: freeze, copy, compare, switch DNS; users on Cloud Run](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-ee9eda5ce10eeb7d7045ebef472f3cedb5597c39d4b4caba012f1003798650ad-680x380-svg/bytes "The move in five steps. Users stay on Heroku until the last one, so every step before it can be redone.")

1. **Find what breaks.** Timers in the code, work after the response, files written to disk, sessions in memory. Exodus's audit lists them with file and line; [the patterns guide](https://exodus.elim.io/blog/patterns-that-break-on-cloud-run) shows each replacement.
1. **Change the code on Heroku first.** Move the worker to HTTP handlers, uploads to a bucket, sessions to cookies. Each change can ship to Heroku and be tested there before anything moves, and each one is small to roll back.
1. **Stand up the new app** at its `run.app` address, with Cloud SQL, the secrets and the schedules, against an empty database or a copy. Deploy from CI with keyless auth, as [deploying from GitHub without keys](https://exodus.elim.io/blog/deploying-from-github-without-keys) shows, not with a JSON key in a CI variable.
1. **Rehearse the copy of the data.** Time it, because that is how long writes will be frozen on the day.
1. **Cut over:** freeze writes, copy, compare, switch the domain. Keep the Heroku app stopped, not deleted, for a while as the way back. [The cutover day checklist](https://exodus.elim.io/blog/the-cutover-day-checklist) has the hour of the switch minute by minute.

## Building the image

You do not need a Dockerfile on day one. `gcloud run deploy` with a source folder builds the code with Google's buildpacks, which read the same `package.json`, `Gemfile` or `requirements.txt` that Heroku's did, and a `Procfile`'s `web:` line if it has one:

```shell
gcloud run deploy web \
  --source . \
  --region europe-west1 \
  --max-instances 3
```

Write a Dockerfile later if the build needs something the buildpack does not do; [buildpacks or a Dockerfile](https://exodus.elim.io/blog/buildpacks-or-a-dockerfile) compares the two.

Google has its own guide for moving a Node.js app from Heroku. It was updated in March 2026, but it still pushes images to Container Registry, which stopped accepting new images on March 18, 2025, and connects to the database through a Serverless VPC Access connector. Use Artifact Registry, which `gcloud run deploy` uses on its own, and the Cloud SQL connection Cloud Run mounts with one flag, as [connecting Cloud Run to Cloud SQL](https://exodus.elim.io/blog/connecting-cloud-run-to-cloud-sql) shows.

## Secrets

Move each config var with a pipe, so its value never lands in your shell history or on a command line:

```shell
heroku config:get STRIPE_SECRET_KEY -a my-app \
  | gcloud secrets create STRIPE_SECRET_KEY --data-file=-
```

Then mount the secret into the service as an environment variable of the same name, and the code does not change:

```shell
gcloud run services update web \
  --region europe-west1 \
  --set-secrets STRIPE_SECRET_KEY=STRIPE_SECRET_KEY:latest
```

The service's identity needs the right to read secrets, which [secrets on Cloud Run](https://exodus.elim.io/blog/secrets-on-cloud-run) sets up. Variables that are not secret, such as `NODE_ENV`, go in as plain environment variables with the set-env-vars flag of the same command. Heroku sets `DATABASE_URL` and `REDIS_URL` itself; on Google you write the new database's address into a secret yourself. Rotate the keys that sat in Heroku's dashboard once the move is done.

## The release phase and the schedules

On Heroku the `release:` command runs before new dynos boot, and a failed release stops the deploy. On Cloud Run, make the migration a job from the same image, and run it before the deploy that needs it:

```shell
gcloud run jobs create migrate \
  --image IMAGE_URL \
  --region europe-west1 \
  --command node \
  --args migrate.js \
  --set-cloudsql-instances PROJECT:europe-west1:db \
  --set-secrets DATABASE_URL=DATABASE_URL:latest

gcloud run jobs execute migrate \
  --region europe-west1 \
  --wait
```

In CI the order is: build the image, update the job to it, execute the job and wait, then deploy the service. [Database migrations as Cloud Run jobs](https://exodus.elim.io/blog/database-migrations-as-cloud-run-jobs) covers migrations that must stay compatible with the old revision while both run.

Heroku Scheduler runs its commands as one-off dynos every 10 minutes, every hour or every day, and its jobs live in the dashboard, not the repository, so list them by hand before you move. Each becomes a Cloud Scheduler job with a cron line and a time zone, calling an endpoint of the service with a token or starting a job; [cron and timers with Cloud Scheduler](https://exodus.elim.io/blog/cron-and-timers-with-cloud-scheduler) has the commands. Three Scheduler jobs per billing account are free.

## The database

For a database under a few GB, a dump and a restore is the whole method. Take a fresh backup on Heroku and download it:

```shell
heroku pg:backups:capture -a my-app
heroku pg:backups:download -a my-app
```

That writes `latest.dump`, in Postgres's custom format. For such a dump Google's page says to list the archive, comment out the extension statements, and restore from that list:

```shell
pg_restore --list latest.dump \
  | sed -E 's/(.* EXTENSION )/; \1/g' > latest.toc
pg_restore \
  --use-list=latest.toc \
  --no-owner \
  --no-acl \
  -d "$CLOUD_SQL_URL" \
  latest.dump
```

Run it from a machine that reaches Cloud SQL, for example through the Cloud SQL Auth Proxy. Create the extensions the app needs on the new database first, with `CREATE EXTENSION`. Then count the rows of every table on both sides and checksum the tables that matter before you switch. Run the new app against the copy and walk its main paths. [Moving a Postgres database without losing data](https://exodus.elim.io/blog/moving-a-postgres-database-without-losing-data) covers the comparison and logical replication with Database Migration Service, for databases where a dump takes hours.

Files uploaded by users go the same way: copy them to a bucket before the day and again for what changed, keeping the keys, so the rows that point at them stay right.

## The domain

Cloud Run's own domain mappings are in Preview, work in 10 regions, and Google says they are not production-ready. For production, put Firebase Hosting in front of the service, or a global load balancer, which bills by the hour; [custom domains and HTTPS](https://exodus.elim.io/blog/custom-domains-and-https-on-cloud-run) compares them. Lower the DNS record's TTL to 300 seconds a day before, switch it in a quiet hour, and watch errors and latency for a day before you turn the Heroku app off. Webhooks from Stripe and OAuth redirect URLs point at the domain, so check each outside service that calls you.

## What it costs

A Basic dyno is $7 a month at most, and a web and a worker are two of them, before the database and add-ons. A Standard-1X is $25. An Essential-0 Heroku Postgres is $5 a month.

On Cloud Run the same web process at a few thousand visits a day often stays inside the free tier, and a worker turned into Cloud Tasks costs cents. The database is usually the largest line: Cloud SQL's smallest instance, `db-f1-micro`, with 10 GiB of SSD is $9.37 a month, more than Essential-0, so a small app's savings come from the dynos, not the database.

![A bar chart of a web process, a worker and Postgres for a month: $19.00 on Heroku with two Basic dynos and Essential-0, $55.00 on Heroku with two Standard-1X dynos and Essential-0, and $11.09 on Cloud Run with Cloud SQL](https://exodus.elim.io/media/v2026-09-16/t/exodus/assets/image-5d98eb2caeba828f765eccbf17e50e8645f848b3834f55dc79c343e1ffbbca9c-680x350-svg/bytes "Heroku and Google list prices, read Oct 12, 2026. Cloud Run's bar is the app at 2,000 visits a day from the costs guide; Cloud SQL is $9.37 of it.")

The Cloud Run bar is the small app priced in [what Cloud Run costs](https://exodus.elim.io/blog/what-cloud-run-costs): $11.09 a month at 2,000 visits a day, with egress and images. The database machines are not the same size, so compare your own app. For the sample above, the Exodus report put Heroku at $22.00 a month, counting the Key-Value Store, and Cloud Run at $10.02 at 500 visits a day.

If the database is small, a serverless Postgres outside Google can bring the whole bill to a few dollars; [Cloud SQL, Firestore or serverless Postgres](https://exodus.elim.io/blog/cloud-sql-firestore-or-serverless-postgres) compares the choices.

## From Railway, Render or Fly

The same map holds for the other platforms:

- A Railway or Render web service, or a Fly app, becomes a Cloud Run service; their background workers become Cloud Tasks or a worker pool.
- Railway cron and Render cron jobs become Cloud Scheduler.
- Render's pre-deploy command and Railway's deploy command become the migrate job.
- Their Postgres becomes Cloud SQL, their variables and environment groups become Secret Manager, and `*.up.railway.app`, `*.onrender.com` and `*.fly.dev` become `*.run.app`.

The CLI reads `railway.json` and `railway.toml`, `render.yaml` and `fly.toml` as it reads a `Procfile`. A Compose file deploys, generally available since March 2026, but as one Cloud Run service with several containers and one instance at most by default, so services that scale on their own deploy one image each. Fly machines that stop and start already behave like Cloud Run; volumes are the part to replace. [Railway or Render to Cloud Run](https://exodus.elim.io/blog/railway-or-render-to-cloud-run) and [Fly to Cloud Run](https://exodus.elim.io/blog/fly-to-cloud-run) go through each.

## Where Exodus comes in

Exodus runs this move with your own coding agent. The CLI audits the repository on your machine, as above, and the report prices Heroku against Cloud Run and lists the changes in order. The plan it writes has eleven tasks for the sample app, from a Dockerfile and a health check to the switch of the domain with Heroku stopped, not deleted, for a week.

## FAQ

### Is Heroku shutting down?

No. Since February 6, 2026, Heroku runs a sustaining engineering model: it keeps running and gets security and stability work, but no new features. Customers who pay by credit card, existing and new, keep their prices and service; new customers can no longer sign an Enterprise contract, and existing contracts are honored.

### What replaces Heroku worker dynos on Cloud Run?

Cloud Tasks calling a handler in your service, one HTTP request per job, for Sidekiq, Celery, BullMQ or RQ workers. A worker that must keep a loop running, such as a stream consumer, goes to a Cloud Run worker pool, generally available since April 2026, which bills all the time it runs.

### How do I move a Heroku Postgres database to Cloud SQL?

For a database under a few GB, capture and download a fresh backup with the Heroku CLI, then restore it into Cloud SQL with pg_restore, skipping the extension statements and the owners. Create the extensions on the new database first, then count the rows of every table on both sides before you switch.

### Is Cloud Run cheaper than Heroku?

For a small app, usually. A web dyno, a worker and Essential-0 Postgres cost $19.00 a month on Basic dynos and $55.00 on Standard-1X; the same kind of app at 2,000 visits a day is $11.09 on Cloud Run with Cloud SQL. The savings come from the dynos, because Cloud SQL's smallest instance costs more than Essential-0.

### What replaces the Heroku release phase on Cloud Run?

A Cloud Run job built from the same image, usually named migrate, which you execute and wait for before the deploy that needs it. In CI the order is: build the image, update the job to it, execute the job and wait, then deploy the service.

## Sources

1. [An update on Heroku, Heroku blog (a sustaining engineering model from Feb 6, 2026, no new features; credit-card customers, existing and new, keep pricing, billing and service; Enterprise Account contracts no longer offered to new customers, existing ones honored; read Oct 12, 2026)](https://www.heroku.com/blog/an-update-on-heroku/)
2. [Summary of Heroku June 10 outage, Heroku blog (from 06:00 UTC on Jun 10, 2025, up to 24 hours of downtime for many customers; the status page ran on the affected infrastructure; Jun 15, 2025, read Oct 12, 2026)](https://www.heroku.com/blog/summary-of-june-10-outage)
3. [Usage and billing, Heroku Dev Center (Basic dyno $7 a month at most, about $0.01 an hour; Standard-1X $25; Standard-2X $50; Eco $5 for 1,000 dyno hours; Essential-0 Heroku Postgres $5 a month; updated Sep 9, 2026, read Oct 12, 2026)](https://devcenter.heroku.com/articles/usage-and-billing)
4. [Release phase, Heroku Dev Center (a release process type in the Procfile; app dynos do not boot for a new release until it finishes; updated Mar 30, 2026, read Oct 12, 2026)](https://devcenter.heroku.com/articles/release-phase)
5. [Heroku Scheduler, Heroku Dev Center (a free add-on that runs jobs every 10 minutes, every hour or every day as one-off dynos, which are billed; read Oct 12, 2026)](https://devcenter.heroku.com/articles/scheduler)
6. [Heroku Postgres backups, Heroku Dev Center (pg:backups:capture; pg:backups:url gives a public URL that expires after 60 minutes; pg:backups:download; updated Sep 26, 2026, read Oct 12, 2026)](https://devcenter.heroku.com/articles/heroku-postgres-backups)
7. [Heroku CLI commands, Heroku Dev Center (heroku config:get KEY; updated Aug 4, 2026, read Oct 12, 2026)](https://devcenter.heroku.com/articles/heroku-cli-commands)
8. [Migrate Node.js apps from Heroku to Cloud Run, Google (updated Mar 4, 2026; builds to Container Registry with gcr.io names and uses Serverless VPC Access; read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/migrate/migrating-nodejs-apps-from-heroku-to-cloud-run)
9. [Container Registry deprecation, Google (Container Registry shut down for writes on Mar 18, 2025; gcr.io repositories hosted on Artifact Registry keep working; read Oct 12, 2026)](https://docs.cloud.google.com/artifact-registry/docs/transition/transition-from-gcr)
10. [Google Cloud's buildpacks, Google (detect the language from the source, and a Procfile can define or override the start process; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/docs/buildpacks/overview)
11. [Export and import using pg_dump, pg_dumpall and pg_restore, Cloud SQL for PostgreSQL, Google (dump with --no-owner and --no-acl in the custom format; before pg_restore, list the archive and comment out the EXTENSION lines with sed, then restore with --use-list; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/sql/docs/postgres/import-export/import-export-dmp)
12. [Map custom domains, Cloud Run, Google (domain mappings in Preview, not production-ready, in 10 regions; Firebase Hosting or a global external Application Load Balancer for production; updated Oct 9, 2026, read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/mapping-custom-domains)
13. [Cloud Run release notes, Google (worker pools generally available Apr 14, 2026; deploying with a Compose file generally available Mar 25, 2026; read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/release-notes)
14. [Deploy services using Compose, Cloud Run docs, Google (one service with several containers, max instances 1 by default; read Oct 12, 2026)](https://docs.cloud.google.com/run/docs/deploy-run-compose)
15. [What's new in Cloud Run at Next '26, Google Cloud blog (SSH into a running container, in preview with select customers; Apr 22, 2026, read Oct 12, 2026)](https://cloud.google.com/blog/products/serverless/whats-new-for-cloud-run-at-next26)
16. [Cloud Run pricing, Google (request-based $0.000024 per vCPU-second, $0.0000025 per GiB-second, $0.40 per million requests, with 180,000 vCPU-seconds, 360,000 GiB-seconds and 2 million requests free a month per billing account; Tier 1 list prices read Oct 12, 2026)](https://cloud.google.com/run/pricing)
17. [Cloud SQL pricing, Google (db-f1-micro $0.0105 an hour, SSD $0.17 a GiB-month; read Oct 12, 2026)](https://cloud.google.com/sql/pricing)
18. [gcloud reference, Google Cloud SDK 587.0.0 (--help of run deploy, run jobs create, run jobs execute, scheduler jobs create http, secrets create, sql instances create, run services update-traffic; read Oct 12, 2026)](https://docs.cloud.google.com/sdk/gcloud/reference/run/deploy)
19. Exodus team: exodus 0.1.3 audit, report and plan on a sample Express app written for Heroku (Procfile with web, worker and release, app.json with Postgres, Redis and Scheduler), Oct 12, 2026
