Moving from Railway
Move a Railway service to Deplane with one command: deplane import railway reads the project on Railway, shows what moves and what stays, then creates the Deplane app with its secrets, workers, cron jobs and release command, copies the Postgres database, adds a Redis, and deploys your code. Railway keeps serving your users until you switch.
The command runs on your own computer with your Railway token, which never leaves it. It only reads Railway (GraphQL queries, never changes). The command and its flags are in the CLI reference, and every error has a fix in Troubleshooting and error codes. It works like Moving from Heroku, which explains the switch-over in more detail.
Before you start
- Install the Deplane CLI and run
deplane login. - Have the service's code in a folder: a checkout of the repository Railway deploys.
- Give Postgres a public address. Railway databases are private by default, and Deplane copies the database from outside Railway. Open the Postgres service → Settings → Networking → Public Networking and add a TCP Proxy. This sets
DATABASE_PUBLIC_URL(a...proxy.rlwy.netaddress), which the import reads. Without it the report has a ✗ line saying so. You can remove the proxy after the switch; Railway bills its traffic as egress. - Create a project token: the project's Settings → Tokens, for the environment to import (usually
production).
The Railway token. A project token reaches one environment of one project, and that is the environment the CLI imports. An account token works too (the CLI then imports the production environment, or the only one), but reaches every project you have. Workspace tokens are not supported yet: use a project token for a workspace's project. Railway has no read-only tokens; the CLI only reads with it. Delete the token when the import is done.
Give the token to Deplane in one of three ways, never as a flag:
- run
deplane import railway my-project --checkin a terminal and paste it at the hidden prompt; - set it for one command:
RAILWAY_TOKEN=... deplane import railway my-project --check; - or pipe it in from a password manager.
A token piped on stdin wins over RAILWAY_TOKEN. The CLI says which one it used, never the token. It sends it only to Railway's API (backboard.railway.com), never writes or prints it, leaves it out of --debug, and never sends it to Deplane.
Which service
deplane import railway PROJECT imports the one service with a public domain. If several services have one, name it: deplane import railway my-project/web. The other services of the environment are sorted in the report:
- Workers: services without a public domain, with a start command and a running deployment, that Railway says are built from the same repository as the app (or the same image). A service with a cron schedule becomes a cron job. A service whose source Railway does not give, or that comes from another repository or image, is never made a worker, and its variables are not copied: the report lists it, to import on its own or add with
deplane processes add. - Databases: the services the app's variables reference, such as
${{Postgres.DATABASE_URL}}. - Other apps: another service with a public domain, or one built from another repository, is listed, to import on its own.
Check first
$ deplane import railway shop --check
Railway service shop (project shop, environment production, region europe-west4-drams3a) → Deplane app shop
Processes
✓ web: shop, 2 replica(s) on Railway → 2 small instance(s).
⚠ Railway has no instance sizes (it bills by use): Deplane starts the app and its workers on small instances ...
✓ pre-deploy command → release command `npm run migrate`.
✓ worker: 1 replica(s) on Railway → worker worker, 1 small instance(s): `node worker.js`.
Data
✓ Postgres (Postgres): copied into a new Deplane database shop-db ... through its public URL ...
⚠ Redis (Redis): a new Deplane Redis shop-redis ... Its data is not copied ...
Config vars
✓ 2 copied as secrets: APP_URL, SECRET_KEY_BASE.
✓ Not copied, Deplane sets them: DATABASE_URL, PORT, REDIS_URL.
✓ Not copied, Railway-managed: RAILWAY_PUBLIC_DOMAIN.
⚠ Not copied, they read from the Postgres or Redis that Deplane replaces: PGHOST. Deplane sets only DATABASE_URL and REDIS_URL ...
⚠ Railway never returns sealed variables through its API, so any you sealed are not copied ...
...
Nothing was changed (--check). Run the command again without --check to import.
Then run the command again without --check (in a terminal it asks first; from a script, add --yes). The flags are the same as for Heroku: --source DIR, --name NAME, --check, --yes.
What moves
| On Railway | On Deplane |
|---|---|
| The service | The app, with as many instances as replicas, all small: Railway has no sizes. Change them with deplane scale. |
| Worker and cron services (see above) | Workers and cron jobs, with their start command and schedule. |
| The pre-deploy command | The release command. |
| Variables, resolved (shared variables and references included) | Secrets. A worker's variables are added; where one sets a name differently, the web service's value is used and the report names it. Not copied: RAILWAY_*, PORT, and the variables that read from the Postgres and Redis Deplane replaces (such as PGHOST or PGPASSWORD: the report names them; Deplane sets only DATABASE_URL and REDIS_URL). |
Postgres (DATABASE_URL = ${{Postgres.DATABASE_URL}}) | A new Deplane database, copied once from DATABASE_PUBLIC_URL (copy mode), attached as DATABASE_URL. |
Redis (REDIS_URL = ${{Redis.REDIS_URL}}) | A new, empty Deplane Redis (Valkey 8, 256 MB), attached as REDIS_URL. |
| The code | Your folder, built by Railpack, or by your Dockerfile if it has one. |
What does not move
- Sealed variables. Railway never returns their values through its API, so the CLI cannot see them, or even tell whether you have any. Set each yourself:
deplane secrets set NAME=VALUE --app APP. - Redis data is not copied.
- MySQL and MongoDB services stay on Railway. Variables that read from them hold their private address (
*.railway.internal), which does not resolve on Deplane: point them at the service's public URL. - Variables that use Railway's private network (
*.railway.internal) are copied, with a warning. - Volumes are not copied. Add a disk and copy the files yourself.
- Cron schedules, pre-deploy commands and sources are read from fields Railway does not document. If Railway refuses them, the report says so, and no worker or cron job is created: add them yourself with
deplane processes add. - Cron schedules Deplane cannot run. Day names (
MON-FRI), month names and 7 for Sunday are converted; anything else Deplane's cron does not take is a ✗ in the report, before anything is created. - Deplane runs in one region, in Europe.
Switch over
The first import is a rehearsal. When the Deplane app works:
- Before the day:
deplane domains add HOSTNAME --app APPfor each custom domain, and add the TXT record it shows. Make sure your plan has room for one more database. - Stop writes on Railway: remove the active deployment of the service and of its workers (each service → Deployments → the active one → Remove). The site is down from here until DNS moves.
- Copy the data again and move the app to the copy in one release:
``sh
deplane db import --name APP-db-final --mode copy --yes # paste DATABASE_PUBLIC_URL at the prompt
deplane db switch APP-db-final --from APP-db
``
- Switch DNS to the target
deplane domains addshowed, and wait for the certificate. - Keep the Railway project until the app has run well on Deplane; then remove the Postgres TCP proxy and delete the project.
- Delete the rehearsal's database:
deplane db destroy APP-db. - Delete the Railway token (the project's Settings → Tokens).
Limits
The Heroku import's limits apply: Postgres up to 5 GB and versions as for every Postgres import, a new 256 MB Redis without its data, 10 workers and cron jobs per app, sizes up to 2 GB, and copying the database needs a person logged in to the CLI. Railway's API allows 100 calls an hour on its Free plan and 1000 on Hobby; an import makes about 5, plus 5 for each service in the environment.
This page is also available as Markdown. AI agents can start at /llms.txt.