← All case studies Heroku → DigitalOcean App Platform

Axolo left Heroku for DigitalOcean App Platform and inherited double crons

Sydney Cohen moved a Node backend in two days. DNS took about 15 minutes on a Saturday. NODE_ENV stayed on Staging for a day, and EasyCron plus leftover Heroku jobs pinged users twice. The $560 versus $65 table is his forecast against the new bill.

Sydney Cohen published this on 28 July 2022. Axolo opens a Slack channel for every GitHub or GitLab pull request. The backend is Node. MongoDB. Redis with Bull. Ten to twenty cron jobs a day. Sentry and Logtail. About 30,000 webhook HTTP requests a day.

They had been scaling Heroku vertically and horizontally as bigger customers arrived. Credits had been gone for more than six months. The next step, as he modeled it, was Performance-M dynos.

If you are looking at the same route, start with the Heroku → DigitalOcean App Platform playbook. Sydney’s post is a two-day cutover with the bugs that survived staging.

The bill he was trying not to grow into

The Heroku line for that one server, without the database, was $210 a month. Three Standard-2x dynos at $50 each, $150. Heroku Redis, 250 MB, $60. Logtail was $0 on that plan.

A Performance-M dyno is $250 and 2.5 GB of RAM. Two of those plus Redis is the $560 he puts in the comparison table. That is a forecast for the next vertical step, not the invoice they were paying the month they left. The live bill at move time was the $210 stack.

DigitalOcean App Platform was the Heroku-shaped option. He linked GitHub, pasted env vars, and kept MongoDB where it was. He created a new Redis on DigitalOcean. Cron is not native. He picked EasyCron rather than a Droplet just to run cron.

Staging moved first. Core paths looked fine. Production moved on a Saturday, when Axolo traffic is quieter. DNS from Heroku to DigitalOcean took about 15 minutes. Requests drained across.

Two bugs that staging did not catch

The production app lived in a DigitalOcean project meant for staging. NODE_ENV was Staging. Analytics and Sentry stayed quiet for a full day.

They turned the Heroku dynos off and put the app in maintenance. They did not delete the Heroku cron jobs. EasyCron was already firing. Each job ran twice. Users got duplicate notifications.

Those are the two production defects he names. Everything else is tradeoff.

What he paid, and what he gave up

Production on DigitalOcean landed at two 2 GB apps at $25 each, plus a 1 GB RAM / 10 GB SSD Redis at $15. $65 a month. The TLDR says $67. The table says $65. I am leaving both.

Build time went from about 2 minutes on Heroku to about 8 minutes on DigitalOcean. He had not tried the beta faster build. The insight dashboard was harder to read. CLI logs lost the color that marked a slow request. Add-ons needed separate accounts. PR review apps had no Heroku-like button. Hatch credits took a month to arrive.

Preboot-style zero-downtime deploys were on by default. Support replies, once past Hatch, took less than a day.

He lists Render and Fly as other Heroku-shaped hosts he did not pick. If those are your next hop, the matching pages here are the Heroku → Render playbook and the Heroku → Fly.io playbook.

What I would copy

Stage first, then cut DNS on a quiet day. Fifteen minutes of mixed traffic is his number. Watch NODE_ENV on the production app, not the project name.

Inventory cron before you idle Heroku. Scheduler add-ons keep running after dynos sleep. Delete them, or you will double-fire like EasyCron plus Heroku.

Do not quote $560 as money they stopped paying. Quote $210 as the old run rate and $65 as the new one. $560 is the Heroku bill he refused to grow into.

Keep Heroku around until a known webhook and a known cron have fired once, and only once, on DigitalOcean. Sydney found the double cron in production.

The Heroku → DigitalOcean App Platform playbook is the checklist. Use his post for the cron leftover a green staging deploy will not show.