Paul Works published I Cut My Rails Hosting Costs by 70%: Migrating from Heroku to Railway on 8 March 2026. This is his own post, not a Railway page.
He had a small Rails API on a Heroku dyno, with JawsDB as the MySQL add-on. The stack worked. The bill did not, for a personal project. He moved the app and the database to Railway in about 20 to 30 minutes and said there was no downtime.
If you are looking at the same route, start with the Heroku → Railway playbook. Railway’s migrate-from-Heroku guide covers a simple web service and Heroku config vars. Paul is the MySQL streaming field report. Railway itself says a database move is a separate job.
Why he left
Heroku’s developer experience was still one of the best he had used. He said that in the post. Cost was the reason to leave.
The old bill was about $20 a month: $7 to $10 for the dyno, $10 to $12 for JawsDB. Those are his ranges, added up. He called $20 not huge, and still too much for a side project.
He wanted one platform for the app and MySQL, and a smaller invoice.
How he moved
Four steps: link the Railway project, dump JawsDB, import into Railway MySQL, then point Rails at the new URL.
He installed the Railway CLI, authenticated, and ran railway link. That ties the local repo to a Railway project and writes a .railway folder. You do it once per repository. railway run then executes a command with that project’s environment variables. The -s flag picks the service.
He did not write a dump file to disk. He piped mysqldump from JawsDB straight into mysql on Railway:
mysqldump \
-h jawsdb-host \
-u jawsdb-user \
-pjawsdb-password \
--no-tablespaces \
--set-gtid-purged=OFF \
--single-transaction \
jawsdb-database \
| railway run -s MySQL mysql \
-h $MYSQLHOST \
-u $MYSQLUSER \
-p$MYSQLPASSWORD \
$MYSQLDATABASE
The host, user, password, and database name in his post are placeholders. I left them as placeholders because he never published the real JawsDB hostname.
--no-tablespaces skips CREATE TABLESPACE statements. mysqldump needs the PROCESS privilege unless you pass this flag. JawsDB’s own backup primer uses --no-tablespaces in the example. Paul says JawsDB sits on AWS RDS, which blocks those privileges. The flag is how the dump completes.
--set-gtid-purged=OFF keeps SET @@GLOBAL.GTID_PURGED out of the SQL. MySQL’s default can emit replication metadata from the source. That statement can fail when the target is a fresh Railway MySQL, not a replica of the same GTID set.
--single-transaction starts a consistent InnoDB snapshot. Rails apps usually sit on InnoDB. You do not lock every table for the whole dump. Writes can still land after the snapshot starts, so the copy is consistent as of that moment, not live-replicated. Paul still called the cutover zero downtime.
He checked the import with SHOW TABLES through the same railway run -s MySQL path. The names he listed were users, transactions, posts, and comments. That is the check he published. He does not give row counts or checksums.
On Heroku, Rails had been reading JawsDB through DATABASE_URL. JawsDB documents that flow: copy JAWSDB_URL into DATABASE_URL, and use the mysql2:// scheme if you use the mysql2 gem.
Railway MySQL injects MYSQLHOST, MYSQLPORT, MYSQLUSER, MYSQLPASSWORD, MYSQLDATABASE, and MYSQL_URL. Paul pointed production at MYSQL_URL:
production:
adapter: mysql2
url: <%= ENV['MYSQL_URL'] %>
Rails already accepts a url key, including an ERB ENV read. After he pushed that change, Railway redeployed the app.
Railway’s official Heroku guide never runs mysqldump. It is railway login, railway init, railway up, railway domain, then import Heroku variables from the canvas command palette. Paul skipped that importer. He moved the database by hand and rewrote database.yml.
What he gained
The new bill was about $6 a month: $3 to $4 for the app, about $2 for MySQL. He called that a 70 percent cut. Those are his side-project numbers in March 2026. They are not Railway’s current price list, and they are not a production forecast.
The stack got simpler. One Railway project held the Rails service and native MySQL. JawsDB as a Heroku add-on went away.
The copy finished in under half an hour on his database. He did not publish the size.
He still says Heroku is a good host for mature production apps. He puts Railway on the side-project side of that comparison.
What broke or got skipped
He does not describe a write freeze. --single-transaction takes a snapshot. Any insert after that snapshot is not in Railway until you copy again, so a busy app can split writes across both hosts. The playbook wants one write authority and a measured final delta.
He also does not describe workers, a Procfile, a custom domain, or a rollback. Railway’s migrate guide still supports only one Procfile process. Extra process types become extra services.
SHOW TABLES is a name check, not a data check. He never published row counts. He never says he left Heroku running as a fallback, or how long.
He did not import Heroku config vars through Railway’s palette. Secrets that were not in database.yml still needed a second pass. He does not list them.
Static outbound IPs were not guaranteed on either host, in his table. If you allowlist the old dyno, that allowlist dies with the move.
What I would copy
Treat his 20-minute window as a small MySQL API, not as a 50 GB production plan. Recalculate dump time on a copy of your own data before you promise a maintenance window.
Keep the three mysqldump flags. --no-tablespaces is what JawsDB and RDS usually need. --set-gtid-purged=OFF keeps replica metadata off a fresh target. --single-transaction is the InnoDB snapshot.
Then freeze writes, or run a second dump, before you flip MYSQL_URL. Paul’s “no downtime” claim does not include that step. I would add it.
After SHOW TABLES, count rows on the tables you care about. Users and transactions are the ones I would reconcile first on an API like his.
Point Rails at MYSQL_URL in database.yml so you do not keep a leftover DATABASE_URL aimed at JawsDB. Leave Heroku up until the Railway logs show the traffic you expect.
Run the Heroku → Railway playbook. Use Paul’s post for the JawsDB-to-Railway MySQL pipe on a small Rails API.