How to migrate from Replit to your own hosting
Following Railway's official Replit migration guide: export to GitHub (free on Starter), translate the .replit run command, migrate Postgres with pg_dump, replace Replit Auth, move App Storage off Google Cloud Storage.
Replit apps are usually full-stack — a server process, a database, secrets, sometimes a worker. That makes them less portable than a frontend-only build, but it also means a single host (Railway) can take the whole project in one step.
There is no single official "migrate away from Replit" guide. Replit's own deployment docs cover hosting on Replit, not external migration (source). The migration is assembled from five official building blocks plus Railway's third-party migration guide:
- Git pane — pushing code to GitHub from inside Replit
- Enterprise privacy settings — confirms Replit has a "source code export as zip" feature (admins can block it)
- SQL Database — Postgres connection string is exposed via env vars, and Drizzle Studio can export data
- Import from providers — confirms that secret values are not imported automatically (and by symmetry, will not export — you re-create them by hand on the new host)
- Railway's Replit migration guide — the third-party doc that ties the above into a working flow
The path below follows that combined sequence and fills in the parts none of them cover.
Plan note. Unlike Lovable, Base44 and Emergent, Replit's GitHub integration is not paywalled — it works on the free Starter tier (source). Starter has limits (1 published project, ~1,200 dev minutes per month) but is enough to push code to GitHub for migration. Core ($25/mo) lifts the publish limit and adds collaborators.
What a Replit project actually contains
The exported repository typically includes:
| File | Meaning | What happens during migration |
|---|---|---|
package.json / requirements.txt / pyproject.toml / go.mod | Stack signal | Railway's Railpack auto-detects from these files |
.replit | Run command and Replit-specific config | Read once for run = "...", then remove |
replit.nix | System dependencies via Nix | Remove — Railway does not use it |
replit.md | Agent instructions / coding preferences (created automatically by Replit Agent in the project root, source) | Keep as reference for future agent work, or delete |
replit.db references or @replit/database | Replit key-value store | Export to JSON, rebuild against Redis or Postgres |
| Replit Postgres connection | DATABASE_URL in Secrets | pg_dump from Replit, pg_restore to Railway/Supabase |
| App Storage (formerly Object Storage) usage | Built on Google Cloud Storage, not S3-compatible (source) | Either keep the GCS bucket externally, or move files to S3 / R2 / Backblaze and rewrite the client |
| Replit Auth | Replit-branded login — users authenticate with their Replit accounts, three scopes: openid, email, profile (source) | Replace with Clerk, Auth.js, Supabase Auth or Auth0 |
Replit's production databases run PostgreSQL 16 or 17 on Neon, so the data layer is standard Postgres — it migrates with normal tooling. The Replit-specific key-value database (App Storage uploads on GCS, and Replit Auth sessions) are the parts that need rewriting, not just dumping.
What syncs to GitHub, what stays in Replit
| Component | In the GitHub-synced repo? | Where it actually runs |
|---|---|---|
| Application source code (Node / Python / Go / etc.) | ✅ yes | Anywhere the language runs |
.replit and replit.nix config | ✅ yes (but ignored outside Replit) | Only interpreted inside Replit |
replit.md agent instructions | ✅ yes | Only meaningful when working with Replit Agent |
| Replit Postgres data | ❌ never | On Replit's managed Neon Postgres |
| Replit key-value DB rows | ❌ never | On Replit |
| App Storage file uploads | ❌ never | On Replit's GCS-backed buckets |
| Replit Auth user accounts + sessions | ❌ never (Replit's auth — they're not your accounts) | On Replit's identity platform |
| Secret values | ❌ never | Stored encrypted in Replit Secrets tab |
Step 1 — Get the code into GitHub
There are two paths depending on whether the Replit project is already connected to GitHub.
Already connected to GitHub
Push any uncommitted changes, then point the new host at the same repository:
git status
git add .
git commit -m "Prepare for external deployment"
git push origin main
No further export is needed.
Not connected to GitHub
- Open the project in Replit.
- Click the three-dot menu in the file tree.
- Choose Download as zip.
- Create an empty GitHub repository.
- Unzip locally and push:
unzip my-project.zip -d my-project
cd my-project
rm -f .replit replit.nix
git init
git add .
git commit -m "Initial export from Replit"
git branch -M main
git remote add origin [email protected]:<user>/<repo>.git
git push -u origin main
git tag pre-migration && git push --tags
Railway's documentation specifies that .replit and replit.nix should be removed before pushing because Railway does not use them (source). Read .replit once for its run = "..." line — that is the start command you will need on the new host — then delete it.
Step 2 — Translate the run command
The run line in .replit is what Replit executes when the Run button is pressed. Translate it to a normal start command:
.replit run command | Production start command |
|---|---|
npm run dev | npm run start (after npm run build) |
npm start | npm start |
python main.py | python main.py, server must bind to 0.0.0.0:$PORT |
uvicorn main:app --reload | uvicorn main:app --host 0.0.0.0 --port $PORT |
node server.js | node server.js, listening on process.env.PORT |
Replit's own deployment guidance specifies that web servers should listen on 0.0.0.0, not 127.0.0.1 or localhost. Most managed hosts inject PORT as an environment variable and route external traffic to it; an app that hardcodes a port will appear to run but never receive requests.
Step 3 — Pick the host
Replit projects fall into three shapes, each with a different best-fit host.
Shape A: Full-stack server app → Railway
Express, Fastify, FastAPI, Flask, Django, or any project with a long-running process. Railway is the cleanest target because the official Replit migration path lands there.
- Push the repo to GitHub.
- Go to railway.com/new → Deploy from GitHub Repo.
- Select the repository. Railway's Railpack reads
package.json,requirements.txt,pyproject.tomlorgo.modand configures the build automatically. - If auto-detection picks the wrong start command, override it under Service → Settings → Build Command / Start Command.
Shape B: Next.js or Vite app → Vercel
A pure frontend, or Next.js with API routes and no separate backend process. Vercel auto-detects the framework, builds on every push, and ships preview URLs per branch. Add the environment variables before clicking Deploy — Vercel only applies env changes to new deployments, so a missed variable means another redeploy.
Shape C: Python ML / data app → Render or Fly
If the project has long-running workers, queues, or anything that does not fit serverless, Render and Fly are both reasonable. Both deploy from GitHub and support Dockerfiles directly.
Step 4 — Move the secrets
Replit stores secrets in the Secrets tab in the left sidebar. Each one is a KEY=VALUE pair stored as an encrypted environment variable. There is no export button. Replit's own import documentation explicitly states that "existing secret values" and environment variable values are not transferred when importing a project from another provider (source) — the same constraint applies when moving the other way. Open the Secrets panel, copy every key and value, and add them to the new host by hand.
For Railway, the fastest path is the Raw Editor under Service → Variables, which accepts the entire block as KEY=VALUE pairs pasted at once. For Vercel, paste each one individually under Project Settings → Environment Variables.
See the hub guide's secrets-moving principles for what to rotate vs migrate, and the public-vs-server distinction that bites when a service-role key ends up in a browser-exposed variable.
Build a .env.example checked into the repo with empty values:
DATABASE_URL=
OPENAI_API_KEY=
CLERK_SECRET_KEY=
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
Never commit real .env files. The example lives in git as a reference for what variables the app needs.
Step 5 — Migrate the database
Working with a coding agent. If the
pg_dump/pg_restoreflow below looks unfamiliar, Claude Code, Codex, or another agent with repo access can run it for you. Open the cloned repo in the agent, point it at this step, and let it handle the commands. The dump and restore both run in your local terminal — the agent can do that inside its CLI session with your approval.
If the app uses Replit PostgreSQL
Replit's SQL Database doc confirms two officially-supported export paths: connect with any PostgreSQL-compatible client using the connection string from environment variables, or export data to a file via Drizzle Studio (source). For a real migration, pg_dump against the connection string is the cleaner option — Drizzle Studio is fine for small/manual exports, but pg_dump handles schema, indexes, sequences and policies in one shot.
Run the dump from the Replit Shell so it can reach the Replit database, then restore against the new host.
# In Replit Shell
pg_dump "$DATABASE_URL" \
--format=directory \
--no-owner --no-privileges \
--verbose \
--file=./db_dump
Download db_dump locally, then restore. For Railway Postgres:
pg_restore \
--dbname="$RAILWAY_DATABASE_URL" \
--format=directory \
--no-owner --no-privileges \
--verbose \
./db_dump
Update DATABASE_URL in the new host's environment. On Railway, use a reference variable (${{ Postgres.DATABASE_URL }}) so the app stays connected if the database is recreated.
After restore: psql "$NEW_DATABASE_URL" -c "VACUUM VERBOSE ANALYZE;" rebuilds query planner statistics. Skipping this leaves Postgres guessing at row counts until the next autovacuum.
If the app uses Supabase
The app is already pointed at a Supabase project. Either keep using it, or create a new Supabase project under your own organization and run the same pg_dump / pg_restore flow (Supabase documents the flags at supabase.com/docs).
If the app uses Replit key-value DB
Replit's key-value store is not Postgres. There is no pg_dump. The options:
- Export to JSON and rebuild against Redis. Iterate every key, serialize, push to Redis on the new host. Requires changing every
replit.dbcall in the application to a Redis client. - Rebuild against Postgres. Same iteration, into a table with
key text primary key, value jsonb. Adds a real query layer but requires re-running every read path.
This is the migration step that surprises people. The data is portable; the access pattern is not.
Step 6 — Replace Replit Auth if used
Replit Auth signs users in with their Replit accounts — the login page is Replit-branded and your app receives an openid, email and profile scope (source). Outside Replit, those sessions do not validate, and your "users" are actually Replit users — you do not own the accounts.
This is the migration step that surprises most teams. Pick a replacement before the cutover:
- Clerk — drop-in for most React / Next.js apps, free tier covers small projects.
- Supabase Auth — fits if the rest of the backend is on Supabase.
- Auth.js (NextAuth) — open-source, self-hosted, more setup.
- Auth0 — heavier, enterprise-fit, more expensive.
Plan for users to re-authenticate after the migration. There is no way to carry Replit Auth sessions across, and the user records themselves live on Replit's identity platform — you can keep their email (if you stored it in your DB) and trigger a signup-or-password-reset flow on the new auth provider.
If the app uses App Storage (Replit Object Storage)
App Storage is built on Google Cloud Storage and is not S3-compatible by default (source). Two options:
- Keep the GCS bucket. Migrate the bucket out of Replit's project to your own GCP project (Google's
gsutilmv works for this), then update your app's credentials to point at it directly. - Move to S3 / R2 / Backblaze. Download every object, upload to the new bucket, rewrite the storage client. Cloudflare R2 implements the S3 API, which keeps the client code simple if you pick R2.
Files migrate as raw bytes either way — there is no "schema" to map. The work is in re-wiring auth, ACLs and the client SDK.
Step 7 — Domain and SSL
Railway issues a .up.railway.app subdomain automatically via Service → Settings → Networking → Generate Domain. For a custom domain, add it in the same panel and update the DNS at the registrar. SSL is issued automatically.
Vercel works the same way via Project Settings → Domains, with DNS records published in the dashboard.
Run smoke tests on the new domain before switching DNS at the registrar. The old Replit deployment can stay live during the transition — having both versions running for a day catches differences in behavior that integration tests miss.
Common gotchas
App runs locally, returns 502 on the host. The server is listening on 127.0.0.1 instead of 0.0.0.0, or on a hardcoded port instead of process.env.PORT. Standard Replit Shell pattern; non-issue on Replit, breaks immediately on Railway / Render.
pg_dump fails with "permission denied for schema." Add --no-owner --no-privileges to the dump command. Replit's Postgres user has different ownership than Railway's, and the restore otherwise tries to assign ownership the new role does not have.
Background jobs disappear on Vercel. Vercel functions terminate when the response is sent. Anything that was running in a background thread on Replit needs a long-running worker on Railway / Render, or a scheduled job.
Frontend assets work, API routes 404. The framework preset on Vercel is wrong, or the project actually has a separate backend that needs its own service. Vercel deploys static + serverless, not long-running Node / Python.
Where to go next
If the project is Next.js or Vite frontend and the backend stays on Supabase, "Migrate from Lovable" covers the same frontend-on-Vercel flow in more detail. If the project has any kind of server process and you want the full deploy walkthrough, the next guide in this pillar — "Your first real deploy: Supabase + Vercel from scratch" — ends with a working URL on a domain you own.
Next in this pillar
How to migrate from Emergent to your own hostingGet the next guide when it lands
One email on Sunday with new /learn guides, tool updates, and a couple of links worth reading.