ReadMe had been on Heroku for eight years. Ryan Park, the one infrastructure engineer on a team of about 20, led the move to Render.
The public write-up is a Render customer interview, so it is a vendor page. I could not find a matching post on ReadMe’s own engineering blog. This case study stays with what Ryan said there, plus the official docs that match the steps.
He planned an hours-long maintenance window. Hard downtime was about 90 seconds. That was the time it took for a MongoDB Atlas instance in Oregon to become the new primary.
If you are looking at the same route, start with the Heroku → Render playbook. Render’s migrate-from-Heroku guide is the generic dump-and-restore path. ReadMe’s cutover was shorter because most of their data already sat in Atlas, not Heroku Postgres.
Why they left
Ryan said they started looking after well-publicized Heroku security and downtime incidents. He also wanted a host that was still investing in customers like ReadMe. In his telling, Heroku had been more focused on Salesforce.
The public record of one of those incidents is Heroku’s April 2022 review. A threat actor used a compromised machine-account token, pulled customer GitHub OAuth tokens from a Heroku database, and cloned some private repos. GitHub published its own OAuth token alert. Heroku then revoked GitHub integration tokens and later rotated customer passwords. The interview does not name that incident. It is the one that matches the kind of news he described.
They wanted a platform that still felt like Heroku. Product engineers should be able to spin up a service and keep it running. AWS managed offerings meant load balancers and certificates before you even had a web app. Render looked closer to a Heroku dyno: custom domain, scaling knobs, a Node.js app. They run the main web app at 20 to 40 instances.
They also wanted something enterprise-ready, including SOC 2. Render documents SOC 2 Type 2 today.
The interview does not give a cost number, so I am not quoting one.
Inventory first
The first job was a list of everything on Heroku.
Some services could die instead of moving. One service stayed on Heroku because they did not want to keep investing in its architecture. It was the only one on PostgreSQL. Most of the data already lived in MongoDB Atlas.
A lot of services used Redis. Those went to Render Key Value, the Render-native Redis-compatible store.
The list is what let them retire unused services and leave the Postgres one behind.
Proof of concept, then load
They stood up the main Node.js monolith on Render first. That app is what users hit. They expected it to be the hard one.
Atlas was in AWS us-east-1. At the time of the interview Ryan said Render did not support that region, so they picked Oregon. Render’s region list now includes Virginia. Treat the us-east-1 gap as a fact about their cutover, not as current Render geography.
Some Heroku API automation did not map cleanly. A few processes went manual. Ryan said Render later closed some of those gaps.
They also found code that assumed Heroku, like logging a dyno name. The proof of concept caught those before they crashed production.
He told the team Render would feel worse for a few months because they were used to Heroku. The move was for bigger reasons than matching every old habit.
They load-tested the proof of concept with the Grafana k6 service. The scripts hit a handful of page types, including an API reference page and a getting-started docs page. Same tests against Heroku and Render. Some pages looked slower on Render. The gap was small enough to keep going. They ran the tests from GCP, from somewhere in the middle of the country, because the old stack was in Virginia and the new one was in Oregon.
A rolling traffic split would have been nicer. Ryan said their infrastructure could not do it. They accepted a short window instead.
Easy services first
Dev and QA moved before production.
Then production, lowest risk first. Hoot is a Twitter clone they use to demo the interactive API explorer. No database. It resets on restart. API Mixtape, the conference marketing site, was important because the conference was around the same time, but it was simple. Deploybert, their internal deploy tool, and the monolith waited until last.
The 90-second cutover
Heroku services had to land on Render. Cloudflare’s origin had to point at Render. Atlas had to move from Virginia to Oregon.
They could have pointed Oregon Render services at the Virginia MongoDB cluster. Ryan said that latency was too high.
So they added three read-only Atlas followers in Oregon first. During the window they stopped writes to the Virginia primary and promoted an Oregon instance. DNS switched at the same time.
Atlas documents this pattern. You add nodes in the new region and then change which region is highest priority so a new primary is elected there. The 90 seconds was that election, because the Oregon replicas were already in place.
What they liked after
They build Docker images elsewhere and pull them from a registry. Ryan said Heroku’s Docker support felt secondary, not fully wired into the API, with undocumented bits.
Environment groups let them share config across services. Render’s migrate guide says services do not share env vars by default, so this matters.
Blueprints (render.yaml) were easier for product engineers than CloudFormation or Terraform.
Render is also a ReadMe customer. He said that existing relationship helped.
On-call stayed light because they keep using managed services, Atlas and Render, instead of running the boxes themselves. That habit predates Render. The move did not change it.
What they left behind
The PostgreSQL service stayed on Heroku on purpose. They wanted to replace it, not port it.
Some automation stayed manual because of API differences.
They accepted a short outage instead of a percentage rollout.
Ryan said load testing is its own science, and their k6 numbers might not be perfectly accurate. He would have preferred to send a slice of real user traffic and measure from the user’s side. They did not have time for that.
What I would copy
Write the inventory before you create a Render service. Mark each app as move, kill, or leave.
Do not force Heroku Postgres onto Render Postgres if the real database is somewhere else. ReadMe’s short window worked because Atlas already replicated. The playbook’s pg_dump / pg_restore path is the default for Heroku Postgres. That path needs a write freeze. Size it with a rehearsal.
Stand the monolith up as a proof of concept. Find the dyno-name logs and the API jobs that will go manual. Load-test from outside both regions if you are also moving the database.
Move a throwaway service first. Then a real but simple one. Then the thing users hit.
If you have to change regions, put followers in the new region before the window. Promote during the freeze. Do not discover replication lag while DNS is already flipping.
The Heroku → Render playbook is the checklist I would run. Use the Render interview for the Atlas cutover, and remember it is a vendor page.