DeployPick

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:

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.

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:

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:

Zeabur → Railway translation table (verified against both platforms' official docs, 2026-10-31).
ConcernOn ZeaburOn Railway
Buildszbpack (Dockerfile first)Root Dockerfile auto-detected, or Railway's builders from your build/start commands
PortGit services default to 8080; HOST=0.0.0.0 injectedPORT provided automatically — listen on 0.0.0.0:$PORT
Variable referencesPlain text, ${VAR} expanded at deploy time; exposed variables shared into other servicesService 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 domainsPrivate 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.

Export from Zeabur (placeholder variables only)
# 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 Railway
# 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.

Export from Zeabur (placeholder variables only)
# 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"
Restore into Railway
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

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.

  1. Test on Railway first. Generate a *.up.railway.app domain for the service and exercise the app end to end against the migrated database and volumes.
  2. Verify database recovery and behavior: row counts, a known-recent write, application smoke tests, and a worker processing one real job.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. Verify production traffic on the new environment: logs, error rates, and a canary of real user paths.
  9. 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.

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:

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

Sources — Railway