Qovery's modernization-services roundup asks which vendors keep a Heroku-to-Kubernetes migration near zero downtime, then answers its own question in a way most vendor content never would: none of them do. Downtime is a sequencing problem, not a product feature. The consultancies run the cutover, the hyperscalers bring single-cloud tooling, the internal developer platforms own day two, and the FinOps tools only matter once you already have a cluster worth optimizing. The six-step cutover sequence is identical no matter whose logo is on the runbook.
That is the honest frame, and this post is a service-by-service read of it: what each category of migration help actually buys you in downtime avoided, what the mechanics require that no vendor can sell you (dual-write discipline, traffic shadowing, DNS cutover rigor), and the costing coda the roundup can't write — the same workloads on owned Hetzner nodes, where the payoff isn't a lower managed bill but no per-instance meter at all.
The honest number up front: ~0 for stateless, minutes for the primary
Near-zero downtime has a floor, and the floor is the database. Stateless HTTP services and queue workers can cut over with effectively zero user-visible interruption using weighted DNS or weighted target groups plus readiness probes and graceful drain. But promoting a new primary database requires a planned write pause, full stop. Google's own Database Migration Service documentation says to "stop all writes, running scripts, and client connections to the source database" and marks that moment plainly: "the downtime period begins here." Budget seconds to a few minutes for that one step, and treat any claim of literal zero on a stateful cutover as marketing.
The full picture, workload by workload:
| Workload | Technique that gets downtime near zero | Realistic user-visible interruption |
|---|---|---|
| Stateless HTTP service | Weighted DNS / target groups + readiness probes | ~0 |
| Queue worker | Graceful drain within terminationGracePeriodSeconds | ~0 |
| WebSocket / long-poll service | Connection draining + client reconnect logic | Brief reconnect blip |
| Primary Postgres/MySQL database | Continuous replication + planned promote | Seconds to minutes (write pause) |
| Read replica | Promote after primary, repoint reads | ~0 |
| Cron / scheduled jobs | Pause on old, resume on new, replay one cycle | ~0 |
| Webhook receiver with IP allowlist | Update allowlist to new egress IPs days ahead | ~0 if pre-staged, hours if not |
Note the last row. The database gets all the planning attention, but the webhook allowlist nobody updated is the classic incident — more on that in the breakers section below. And the write-pause floor is corroborated outside the roundup: a February 2026 migration writeup reports per-service cutovers "sometimes under a minute, when teams rehearse the cutover and build automation around it." Rehearsal is doing the heavy lifting in that sentence.
The six steps that do the work (and the three limits that bound "zero")
The repeatable sequence, compressed from the roundup's playbook:
- Inventory and containerize. Run a 12-factor audit, move config into environment variables, and produce OCI images — via Dockerfiles, or via Cloud Native Buildpacks / Paketo with no Dockerfile at all.
- Build the target before touching production. Stand up the cluster, ingress, CI/CD, secrets, admission policy, and observability while Heroku keeps serving every request. Keep the old platform live as your fallback for at least one full billing cycle. Parallel running is not wasted money; it is the cheapest insurance you will buy all quarter.
- Data first, and hardest. Replicate continuously into the new database — Postgres logical replication, AWS DMS with change data capture, or Google Cloud Database Migration Service — then promote. This is the step with the write pause.
- Shadow, then shift traffic incrementally. Mirror production traffic to the new target before serving from it so you can diff responses against the real thing, then move weight with short-TTL weighted DNS records, weighted ALB target groups, and canary or blue-green rollouts (Argo Rollouts, Flagger) with written error-rate and p95 abort thresholds. Write the thresholds down before you start, not during the incident.
- Keep the exit rehearsed. Rehearse the rollback for real on staging — a rollback you have never executed is a hope, not a plan — and consider a short dual-read or dual-write window for the single riskiest service.
- Keep deploys downtime-free afterward. Readiness probes so traffic never hits an unready pod, PodDisruptionBudgets so node drains can't take your last replica, rolling updates tuned deliberately (Kubernetes rolls 25% at a time by default), and graceful shutdown inside the 30-second termination grace period so in-flight requests and queue workers drain cleanly.
Three hard limits bound how close to zero this gets, and all three live in step 3:
- DMS CDC is not real-time. AWS DMS change-data-capture latency can grow to minutes under load. Size the replication path for peak write throughput, not the average.
- Logical replication doesn't replicate everything. Postgres logical replication skips DDL, sequence counters, and large objects. Handle those by hand or they bite at promote time.
- The promote needs the pause. There is no tooling that promotes a primary without stopping writes first. Anyone promising otherwise is overselling.
One more honest calibration from the roundup: plan 6–12 weeks end to end for a 10–40 service application, with data replication and DNS propagation eating more calendar time than containerization. A 100+ service estate, or a monolith with no test suite, is a 6–12 month program, not a quarter.
Service by service: who buys you which minute
The roundup maps the market into four buyable categories. Here is what each one actually contributes to keeping downtime near zero:
| Category | Vendors | Downtime contribution | Verdict |
|---|---|---|---|
| Migration consultancies | OpsTree, Tasrie IT Services, Mirantis | They run steps 1–5 as a project: assessment, containerization, cluster build, CI/CD wiring, the cutover itself | Buys you the cutover — then the engagement ends and the cluster is yours |
| Hyperscaler / vendor pro services | Google Cloud Consulting + Migrate to Containers, AWS Professional Services, IBM Consulting + OpenShift | Deepest single-cloud tooling (Migrate to Containers generates Dockerfiles + manifests from VM workloads) | Buys you the cutover on one cloud, engagement-priced |
| Internal developer platforms | Qovery, Humanitec, Port, Northflank, Porter | Almost nothing during the cutover — they own day two: git-push deploys, preview envs, managed upgrades, RBAC | Buys you every deploy after go-live, not the migration |
| FinOps / governance add-ons | Kubecost/OpenCost, Cast AI, nOps | Zero — they optimize a cluster you already run | Buy only once you have real cluster spend to attribute |
The pairing that works most often is a consultancy or hyperscaler service for the cutover plus a platform for the years after. And four red flags, worth quoting because they generalize beyond any vendor shortlist:
- A fixed-price bid that excludes data migration has excluded the hardest part. Step 3 is where migrations die; a bid without it is a runbook with a logo on it.
- Literal-zero promises on a stateful cutover mean the vendor doesn't understand the problem or hopes you don't.
- A cluster built around PodSecurityPolicy — removed in Kubernetes 1.25 — means the playbook is years stale. The successor is Pod Security Admission plus Kyverno or OPA Gatekeeper.
- Case studies cited as vendors. Spotahome and Zalando publish useful Kubernetes write-ups; they are engineering organizations, not services you can hire. (Zalando's containerization write-up is genuinely worth reading as evidence.)
Don't lump the IDP category together, either: Humanitec orchestrates a platform you assemble, Port is the catalog and portal on top of infrastructure someone else runs, and Qovery, Northflank, and Porter are runtimes that provision and operate clusters inside your own cloud account. Different shapes, different day-two ownership.
The breakers that aren't the database
Incidents during Heroku exits are rarely the database. They are the dependencies the migration plan forgot to inventory. Make this list before touching DNS:
- Third-party webhook IP allowlists. Your egress IPs change with the platform. Update every allowlist days ahead, or webhooks fail silently — the classic incident.
- Static egress IPs. If any integration keys off a fixed source IP, provision and register the replacement before cutover.
- Cron jobs firing mid-cutover. Pause on old, resume on new, replay one cycle. Double-fired billing runs and missed cleanup jobs are both bad.
- Sticky sessions. If anything still relies on session affinity, drain it before shifting weight or users land on backends without their session.
- Long-lived WebSocket connections. They don't survive a DNS flip gracefully; ship client reconnect logic first and expect a thundering herd on mass reconnect.
- Background jobs with no idempotency. During any dual-run or replay window, a non-idempotent worker processes the same job twice. Idempotency keys before cutover, not after the duplicate-charge ticket.
Real migrations confirm the shape of this list. Rainforest QA's Heroku-to-Kubernetes move found dump/restore "taking several days" and had to reach for logical-replication tooling (Teleport) instead — the data path always costs more than the plan says, and everything around it needs its own checklist line.
Costing coda: the same workloads with no per-instance meter
The roundup frames the Heroku exit in hard numbers — Standard-2X at $50/month, Performance-M at $250/month, Render and Fly.io in the same per-instance band — and prices the destination as managed Kubernetes in your own cloud account, where Savings Plans and committed-use discounts stay in your name. Fair as far as it goes. But there is a destination the roundup can't price, because no vendor in it sells it: owned hardware, where the migration's payoff isn't a lower managed bill but no per-instance meter at all.
Take a representative mid-size stack: 6× Standard-2X dynos ($300), Postgres Standard-0 ($50), and a Redis Mini ($15) — roughly $365/month on Heroku, before Heroku's own Postgres/Redis tiers climb with data size. The same shape fits comfortably on a single Hetzner AX52-class dedicated box (8 cores, 64 GB RAM, 2× 1 TB NVMe, $70/month) with Postgres and Redis self-managed on spare capacity — or an AX42-class box ($55/month) for a smaller shape. That is not a 30% discount; it is the meter disappearing. (The full three-shape math across Heroku, cloud Kubernetes, and owned hardware lives in this site's earlier cost-pattern post; the coda here is the conclusion, not the spreadsheet.)
The honest caveat, kept to its one paragraph: the roundup's ops-tax accounting still applies. Kubernetes ships about three minor releases a year with roughly fourteen months of patch support each, which means upgrades, CVE patching, node-pool management, and an on-call rotation that didn't exist when Heroku ran it for you — plus the observability tooling Heroku bundled and one full billing cycle of paying for both platforms during the parallel run. Under roughly ten services with no compliance deadline, staying put usually wins. Own the math before owning the machines.
Decide day two before day one
The roundup's closing line is the one worth keeping: migrations don't fail at the cutover. They fail six months later, when the cluster is green but nobody wants to deploy to it, the upgrade nobody owned is overdue, and the governance meant for "later" never got added. Buy expertise for the cutover, buy a platform for day two, buy FinOps tooling only once there's a cluster worth optimizing — and name the on-call owner for twelve months out before you touch DNS.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Star the repo on GitHub or deploy your first app today.



