How to Migrate from Zeabur to Railway
Zeabur is shutting down on December 31, 2026, and this guide moves one project — web services, workers, databases, volumes, and domains — onto Railway with a verified, reversible cutover. Railway operations below were checked against docs.railway.com on 2026-10-31; Zeabur behaviors come from Zeabur's official announcement and migration guide. Where a step depends on your setup rather than a documented platform behavior, it says so.
There is no lossless one-click migration for everything, and this guide never promises zero downtime — the cutover in step 6 is a short, controlled window you choose. DeployPick takes no affiliate fees; capability and pricing facts come from verified knowledge-base data (2026-10-30.1; pricing catalog 2026-10-30.2).
1. Inventory what you run on Zeabur
Before moving anything, write down what exists. Zeabur's official guide documents the platform model — each project/environment maps to an isolated namespace with Services (deployments), Volumes, Domains, and environment variables — and your inventory should cover:
- Service sources: which services deploy from Git repositories (with branch and root directory) and which deploy prebuilt images from Zeabur's registry. Registry-hosted images will not be pullable after shutdown and are pruned by a daily cleanup job — those must move to a public registry or be rebuilt from source.
- Long-running services vs workers: web services, background workers, and anything that must stay always-on.
- Data stores: PostgreSQL, MySQL, and Redis instances — plus which are persistent data and which are throwaway caches.
- Volumes and mount paths: every volume, its mount path, and which files it holds.
- Variables: all environment variables per service.
variable listandproject exportreturn values with${VAR}references unexpanded — resolve them against the raw definitions by hand so nothing is lost in translation. - Custom domains: each domain, its DNS record type, and where DNS is managed. Zeabur used A records for dedicated servers and CNAMEs for shared clusters — Railway will give you its own target in step 6.
- Scheduled jobs (cron): expressions and what they run.
- Internal communication: every
*.zeabur.internalhostname and*.zeabur.appURL your services call — none of them will resolve after the shutdown. - Current backup method: what backs each service's data up today, and whether you can restore it yourself.
Also note which kind of Zeabur deployment you are on — rented dedicated server, your own server, or shared cluster (see the notice above). It sets your real deadline: rented dedicated servers close at their own expiry between 2026-12-01 and 2026-12-31; shared clusters stop on 2026-12-31.
2. Back up first — and prove the backup restores
Take your own exports while everything can still be restarted, and verify each one by restoring it somewhere disposable. Do not rely on Zeabur's automatic daily backup as your copy of record — you control neither its schedule nor its availability after shutdown.
- PostgreSQL: dump with
pg_dumpfrom a client whose major version matches the target server (Zeabur's guide warns againstpg_dumpallfor this purpose). Command in step 4. - MySQL: dump with
mysqldump(step 4). - Redis: decide first — pure caches need no backup at all; persistent Redis needs an actual export (step 4).
- Volumes: copy files off-platform (see step 5 for the Railway side).
- Container images: anything that exists only in Zeabur's registry must be re-pushed to a public registry or rebuilt from source.
Store backups somewhere Zeabur does not control, keep credentials out of file names and commit messages, and record the checksum plus the restore command next to each dump. A backup is proven the moment it restores.
3. Recreate services on Railway
Create a new Railway project and add one service per Zeabur service. Two verified paths:
- GitHub repo: connect the repository Railway will deploy from. If a
Dockerfileexists at the service root it is used automatically;RAILWAY_DOCKERFILE_PATHoverrides the location. Otherwise Railway builds with its own builders from your build/start commands. - Docker image: deploy a prebuilt image from a registry you control (not Zeabur's).
Keep the same shape you had on Zeabur: a web service per user-facing app, a separate service per worker (workers do not need a public domain), one database service per database. Differences from Zeabur to re-express, all verified in Railway's docs:
| Concern | On Zeabur | On Railway |
|---|---|---|
| Builds | zbpack (Dockerfile first) | Root Dockerfile auto-detected, or Railway's builders from your build/start commands |
| Port | Git services default to 8080; HOST=0.0.0.0 injected | PORT provided automatically — listen on 0.0.0.0:$PORT |
| Variable references | Plain text, ${VAR} expanded at deploy time; exposed variables shared into other services | Service references like ${{ postgres.DATABASE_URL }} and shared variables like ${{ shared.STRIPE_KEY }}; changes are staged and deployed on review |
| Internal addressing | *.zeabur.internal names, *.zeabur.app domains | Private networking between services in the same project; Railway provides private-domain variables for each service |
Re-entering variables: paste an .env file in the service's RAW editor, or let Railway auto-detect .env files in a connected GitHub repository. Resolve every unexpanded ${VAR} from your step-1 inventory into the Railway reference syntax — for example a Zeabur-style literal postgres://… connection string becomes DATABASE_URL=${{ postgres.DATABASE_URL }} once the database service exists. Sensitive values can be sealed (hidden after entry).
4. Migrate databases
Railway offers one-click PostgreSQL, MySQL, and Redis server templates (plus MongoDB). Two honest caveats before the commands: Railway considers database services unmanaged — configuration, maintenance, and backups are yours — and every database server template carries connection variables plus optional public access over a TCP proxy (host and port shown in the service).
PostgreSQL
Add the PostgreSQL template to your project first, then dump from Zeabur and restore into Railway. Match major versions on both ends (same-major pg_dump/pg_restore is the documented safe path); if the target major version differs, plan a version upgrade as its own step with its own backup, and check that every extension you use is available on the target before cutover.
# Run from a machine whose pg_dump major version matches the TARGET server
# (Zeabur's guide: same major version for dump and restore).
pg_dump --format=custom --no-owner --no-privileges \
--host="$SRC_PG_HOST" --port="$SRC_PG_PORT" \
--username="$SRC_PG_USER" --dbname="$SRC_PG_NAME" \
--file="zeabur-$SRC_PG_NAME-backup.dump"# Restore into the new Railway PostgreSQL service (values from its
# Variables tab / TCP proxy). --clean makes the restore repeatable.
pg_restore --clean --if-exists --no-owner --no-privileges \
--host="$DST_PG_HOST" --port="$DST_PG_PORT" \
--username="$DST_PG_USER" --dbname="$DST_PG_NAME" \
"zeabur-$SRC_PG_NAME-backup.dump"If your app writes continuously, freeze writes (or pause the writers) during the final export in step 6 — a dump taken while rows change is not a consistent snapshot. Verify row counts and a known-recent row on the Railway side before cutover.
Redis
Split your Redis use into cache versus persistent data. A pure cache (session store you can rebuild, rate-limit counters, hot-layer cache) does not need migration at all: create a Redis service on Railway and point the app at it. Persistent data (queues with jobs you cannot lose, dedup sets) must be exported — the exact export path depends on how your Redis instance persists (RDB/AOF) and is not prescribed by either platform's migration guide: follow your Redis version's official persistence docs for the dump command, then import it into the Railway Redis service. Treat this as the step most worth a dry run.
MySQL
Railway officially provides a MySQL server template, so the same dump-and-restore shape applies. The commands below are standard practice — verify the flags against your MySQL version's documentation before running them against production data.
# Flags per standard mysqldump practice — verify against your MySQL
# version's docs. Enter the password interactively (--password without
# an argument): never paste it into the command line or a commit.
mysqldump --single-transaction --no-tablespaces \
--host="$SRC_MYSQL_HOST" --port="$SRC_MYSQL_PORT" \
--user="$SRC_MYSQL_USER" --password \
"$SRC_MYSQL_NAME" > "zeabur-$SRC_MYSQL_NAME-backup.sql"mysql --host="$DST_MYSQL_HOST" --port="$DST_MYSQL_PORT" \
--user="$DST_MYSQL_USER" --password \
"$DST_MYSQL_NAME" < "zeabur-$SRC_MYSQL_NAME-backup.sql"5. Storage and networking
Volumes
Zeabur volumes are node-bound claims — they move by export/import, never by re-attach. On Railway, attach a volume to the service and set its mount path. With Railway's builders the app lives under /app, so a relative ./data directory corresponds to mounting the volume at /app/data; Railway injects RAILWAY_VOLUME_MOUNT_PATH so the app can find its mount. Two documented behaviors to plan around: volumes mount when the service starts (not during build or pre-deploy commands), and growing a volume requires a service restart. Upload your exported files over the transport you verified in your dry run.
Networking
- Replace every
*.zeabur.internalhostname with Railway's private networking between services in the same project, or with a service reference variable. - Nothing may keep calling
*.zeabur.appURLs — grep your config and code for the suffix before cutover. - Non-HTTP services (databases exposed externally, TCP workloads) use Railway's TCP proxy (host + assigned port) rather than an HTTP domain.
- Re-verify WebSockets, workers, and cron after the move: WebSocket connections are supported on Railway without duration limits; recreate Zeabur scheduled jobs as Railway Cron Jobs (crontab expressions; overlapping runs are skipped rather than queued — check that semantics match your current expectations).
Distinguish temporary files (rebuildable caches, build artifacts) from persistent data (user uploads, databases): only the latter need a volume. Temporary paths can stay on the container filesystem and reset on each deploy — which is usually what you want.
6. Domain and cutover — in this order
The cutover is a sequence, not a switch. Never change production DNS before the new environment is verified.
- Test on Railway first. Generate a
*.up.railway.appdomain for the service and exercise the app end to end against the migrated database and volumes. - Verify database recovery and behavior: row counts, a known-recent write, application smoke tests, and a worker processing one real job.
- Plan the write freeze or incremental sync. Decide when writes stop on Zeabur (a maintenance window) or how changed rows will be re-synced. Anything with continuous writes needs the freeze from now on.
- Final data sync. Suspend services on Zeabur (the official flow: suspend → final sync), take/apply the final dump or file sync, and confirm the Railway side is current.
- Lower the DNS TTL now if you haven't — Zeabur's guide recommends 300 seconds ahead of the switch so caches honor the change quickly.
- Switch DNS to Railway's assigned target. In the service's settings add the custom domain; Railway shows the exact record to create — a CNAME to a target like
g05ns7.up.railway.app(the example is illustrative; use the value your dashboard shows). Apex/root domains need a DNS provider with CNAME flattening (Cloudflare CNAME or DNSimple ALIAS; Route 53 and GoDaddy do not support it at the apex). If the domain sits behind Cloudflare's proxy, use Full — not Full (strict) — until the certificate is live. Propagation can take up to 72 hours, usually far less. - Check HTTPS. Railway issues and renews Let's Encrypt certificates automatically once the DNS record verifies — confirm the padlock and the certificate dates before announcing anything.
- Verify production traffic on the new environment: logs, error rates, and a canary of real user paths.
- Keep a written, executable rollback: the old DNS values, the suspended (not deleted) Zeabur services — restarting them clears the suspended state — and the exact steps to re-point DNS back. Per Zeabur's guide, rollback is: restore the DNS record, restart the Zeabur services, verify.
During the 300-second TTL window both environments may receive traffic; that is normal and harmless if writes stay frozen until you verify. Only after verification (step 7) should you consider deleting anything on Zeabur — and given the shutdown, simply leaving it suspended is the safer default.
7. Post-migration verification checklist
Work through every line before you call the migration done — each failure caught here is caught while rollback is still one DNS change away.
- HTTP and HTTPS both serve the new environment; redirects and the certificate are correct.
- Database connectivity from the app on Railway; connection pool behavior under a few concurrent requests.
- Workers process jobs from the queue — enqueue one real job and watch it complete.
- Cron jobs fired at their schedule (check after the first interval, or trigger manually once).
- WebSocket endpoints connect and stay connected through a idle period.
- Volume data survived: write a file, restart the service, read it back.
- Environment variables resolved — no literal ${VAR} or empty expansions anywhere in app config or logs.
- Logs and metrics arrive in Railway's dashboard; alerts you relied on are re-created.
- One deploy-and-rollback drill on Railway: ship a trivial change, then roll it back, so you know the mechanism works.
- Data consistency: row counts match the final sync, and a spot check of recent records on both reads and writes.
- A backup schedule exists for the new environment — Railway volume backups (manual or scheduled) or your own export jobs; the first backup has been taken.
8. Decide whether Railway is right
Railway is one verified destination, not the only one. If anything in this guide gave you pause — unmanaged databases, volume restart semantics, the private-networking model — compare honestly before you commit:
- Zeabur Alternatives: Where Should You Move Your Apps? — verified capability comparison plus a live ranking driven by your requirements.
- Railway vs Render — head-to-head comparison — the two most common Zeabur landing spots, on verified facts.
- The full Deployment Advisor — rank all verified platforms for your exact workload.
- Visit Railway (external site) — official docs and signup (external site)
DeployPick takes no affiliate fees or placement payments from any provider; this guide links only verified official documentation and our own comparison tooling.
Sources — Zeabur
- Zeabur is shutting down — Zeabur, verified 2026-10-31. Official announcement, published 2026-10-17. Timeline for dedicated servers and shared clusters; data-deletion and final-export deadlines announced separately.
- Migrate from Zeabur (official guide) — Zeabur, verified 2026-10-31. Zeabur's own migration reference: deployment types, platform model (k3s, namespaces, PVC volumes, env-var expansion), database export rules, and the recommended suspend → final-sync → DNS cutover flow.
Sources — Railway
- Railway documentation — Railway, verified 2026-10-31
- Variables guide (reference syntax, shared variables, RAW editor) — Railway, verified 2026-10-31
- Databases guide (PostgreSQL, MySQL, Redis, MongoDB templates) — Railway, verified 2026-10-31
- Volumes guide (mount paths, backups, runtime injection) — Railway, verified 2026-10-31
- Public networking (domains, TLS, DNS propagation) — Railway, verified 2026-10-31
- Backups reference (schedules, retention, restore semantics) — Railway, verified 2026-10-31
- Dockerfiles guide (detection, custom paths, build args) — Railway, verified 2026-10-31
- CLI reference (login, run, shell, ssh) — Railway, verified 2026-10-31