38 Apps in One Day: Migrating an Empire from Replit to Coolify

TL;DR
Migrating 38 apps from Replit to Coolify in one day with parallel Claude Code subagents, gh and neonctl - the recipe and real stats on stubs and false-empties.
Last updated: May 17, 2026
The Hook#
38 apps. One day. Roughly 120 pull requests across the empire. By the time the dust settled, every Replit-hosted project under our org had a migrate/coolify-clerk PR sitting in review, pre-staged for one-click Coolify deploy.
This is the candid version. What worked, what was a stub that did not need migrating, and what the recipe actually looks like when you repeat it 38 times in a day.
If you are deciding between a managed deploy platform and self-hosting, start with Coolify vs Vercel to pick your default.
Why We Moved Off Replit#
Replit was a great place to scaffold something at 1am. It is not where you want production infra to sit long-term.
The reasons stacked up:
- Vendor lock-in on the runtime. Replit's Nix layer, deployment targets, and proprietary databases meant every app carried a small but real "this only runs here" tax. Moving them was easier than continuing to pay it.
- Runtime quirks at scale. Cold starts, opaque crash loops, and a control plane we did not own. When 38 apps are all using the same hosting layer, every quirk multiplies.
- No infra parity with the rest of our stack. The serious DD apps already lived on Coolify (Hetzner) with Neon Postgres, Clerk auth, and Cloudflare DNS. Splitting hosting providers meant two debugging playbooks, two billing surfaces, two sets of secrets.
- Cost. We were paying for always-on Replit deployments for apps that get a few hundred hits a week. A Hetzner box running 30+ containers is cheaper than the Replit equivalent by an order of magnitude.
The decision was not "Replit bad." It was "one stack, one playbook, one bill."
The Recipe#
Every migration followed the same shape, regardless of whether the app was Express or Next.js:
For Express + Vite + Drizzle apps:
- Strip Replit-specific files (
.replit,replit.nix, runtime polyfills). - Add Clerk for auth, replacing whatever Replit Auth shim was in place.
- Move the database to Neon Postgres, point Drizzle at the new connection string.
- Wrap it in a single-container Dockerfile that builds the Vite frontend and serves it from Express.
- Add
coolify.jsonplus health check endpoint.
We covered the Neon side of this pattern in more depth in our Convex-to-Neon playbook across four apps, and Clerk was the drop-in auth swap in every app.
For Next.js + Prisma apps:
- Same Replit cleanup.
- Clerk for auth.
- Neon for the Postgres, Prisma migrations rerun against the new DB.
- Single-stage Dockerfile using the standalone Next.js output.
- Coolify config plus
/api/health.
The single-container constraint was deliberate. Coolify is happiest when an app is one container with one port. No sidecars, no multi-service compose files unless the app genuinely needs them. Most did not.
The Honest Stats#
The 38-app number sounds impressive until you break it down:
- 38 repos targeted. Pulled from the org-wide list of anything tagged or known to have Replit deployment history.
- ~16 were genuine app migrations. Real code, real users, real database, real port to do.
- ~9 were empty stubs. Repos scaffolded during a brainstorm, never actually built. The migration agent correctly skipped these and filed an "empty stub, no action" report.
- ~5 were monorepos. A single repo containing 2 to 4 deployable apps. Each got its own
migrate/branch with the apps split into separate Coolify services. - ~4 were false-empties. Looked empty at first pass because the actual app lived in a subdirectory or behind a non-default branch. The agent flagged these for human review rather than guessing.
- ~4 were already migrated. Drift from a previous half-finished migration attempt. We closed those out and noted the existing deploy.
Total PRs opened across all categories: roughly 120. That includes the migration PRs, follow-up cleanup PRs (lockfile sync, env var fixes, health check tweaks), and a handful of chore: archive PRs for repos that should not have existed in the first place.
The Tooling#
The fan-out was the interesting part.
The pipeline was three CLIs and one orchestrator:
ghCLI for everything GitHub. Listing org repos, cloning, branch creation, PR open, PR comment, tagging reviewers. Every agent usedghand onlygh.neonctlfor spinning up Neon Postgres branches per app. New project, new connection string, dump it into the env file, done.- Claude Code subagent fan-out as the orchestrator. The parent session held a queue of 38 repos. It dispatched one subagent per repo, each cloning to its own
/tmp/<slug>/directory to avoid the in-place collision problem we have hit before with 5+ parallel agents on the same checkout.
At peak, we had 8 to 10 subagents running concurrently. Each one followed the same replit-to-coolify skill: clone, audit, decide if migration is needed, apply the recipe or honest-skip, open a PR on a migrate/coolify-clerk branch, tag the reviewer, exit.
The honest-skip rule was load-bearing. Without permission to skip, an agent will hallucinate work to fill the silence. With it, the empty stubs and false-empties got flagged correctly instead of receiving fake migration PRs.
Ship Status#
Every PR is sitting in review right now, tagged @devin-ai-integration for the automated review pass. The standing rule held across all 120 PRs: branch, PR, tag Devin, never direct-push to main.
Each migration PR is pre-staged for one-click Coolify deploy. The Dockerfile builds, the health check responds, the env vars are documented in the PR body. When Devin signs off and we merge, Coolify picks up the push and deploys.
We are merging in batches rather than all at once. Five to ten apps per evening, watch the Coolify queue, fix anything that breaks the build (usually a pnpm-lock.yaml sync issue, the recurring failure mode), move to the next batch.
What's Next#
Once everything is on Coolify:
- Decommission Replit deployments after a 7-day grace period of dual-running.
- Standardize the observability layer. Every app gets the same Sentry config and the same
/api/healthshape, so the empire dashboard can poll one endpoint per app and get a real signal. - Consolidate Neon projects. 38 separate Neon projects is too many. Group by tier and traffic so the free tier covers what it should and the paid tier covers what actually needs it.
- Write the
replit-to-coolifyskill into the standard scaffold. New apps should never touch Replit again. The skill is now part of the default scaffold path.
The interesting part of the day was not the migration itself. The recipe is boring once you have it. The interesting part was that 38 apps moved in a day because the orchestration was tight, the skip rule was honored, and every agent had the same playbook.
That is the leverage. Not the agents. The playbook the agents share.
FAQ#
Why move from Replit to Coolify instead of another managed platform?#
The deciding factors were vendor lock-in on Replit's Nix runtime, cold starts and opaque crash loops at scale, and the fact that the rest of our production stack already ran on Coolify with Neon Postgres and Clerk auth. Consolidating to one hosting playbook removed the two-billing-surface, two-debugging-playbook overhead of running both.
What stack did each migrated app end up on?#
Auth moved to Clerk, the database moved to Neon Postgres, and each app was wrapped in a single-container Dockerfile with a coolify.json config and a health check endpoint, deployed on Coolify.
How were 38 apps migrated in a single day?#
By fanning out Claude Code subagents, one per repo, each working from its own /tmp/<slug>/ clone to avoid collisions. Every subagent followed the same audit-then-migrate-or-skip recipe and opened a PR rather than pushing directly to main.
What happened to repos that turned out to be empty stubs?#
The migration agents were given permission to file an "empty stub, no action" report instead of manufacturing fake migration work. About 9 of the 38 targeted repos fell into that category, and roughly 4 more were false-empties where the real app lived in a subdirectory or non-default branch.
Continue Reading#
- AWS Billing Bug Shows Trillion-Dollar Estimates, Causes Developer Panic
- Best AI Code Review Tools in 2026: CodeRabbit vs DeepSource vs Greptile Compared
- GitLab to GitHub Migrations Go GA: gh gl2gh, What Moves and What Doesn't
- Replit Agent 4: Design-to-Full App with Parallel Agents and Infinite Canvas
- How We Patched 100+ PRs Across Our App Empire in One Day
Get the next deep dive like this in your inbox
One email a week on Coolify and the rest of the AI dev stack. Free.
Read next on Claude Code
Build Log: Turning the DevDigest Blog Into an Agent Content System
How the DevDigest blog became an agent content system: posts, tags, RSS, search, llms.txt, and content expansion reports, plus what not to automate blindly.
9 min readHow I'm Building 24 AI-Powered Apps in Parallel
One dev, one CLI, 24 subdomains, and a lot of parallel agents. The stack, registry, agent loop, and honest lessons behind shipping an AI app portfolio.
9 min read219 PRs in One Day: A Parallel Agent Fan-Out Postmortem
Postmortem of 219 PRs in one day from 200+ parallel Claude Code subagents across 35 repos: what worked, what broke, and the patterns codified into a skill.
8 min readTechnical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.






