Moving from Railway

miosa import railway reads a Railway project through Railway’s public API and writes a plan: the MIOSA project, Deploy targets, services, databases and variable names it would create, and everything it cannot carry across. Nothing is created until you review the plan and apply it.

The same page is the reference for what maps, what does not, and what to do about the secrets.

Before you start

  • A Railway token: an account, workspace or project token from Railway’s account settings. MIOSA reads it, never stores it, and never writes it to the plan.
  • The MIOSA CLI, @miosa/cli 2.4.0 or newer:
npm install -g @miosa/cli
miosa --version
  • Assume every secret in the Railway project is compromised. Railway credentials were exposed in 2025, so the import copies names, not values, unless you explicitly ask otherwise.

The three steps

export RAILWAY_TOKEN=...                     # or --token-file, or a prompt
miosa import railway --project acme-api      # review only; nothing is created
miosa import railway --project acme-api --out plan.json
miosa import railway --apply --plan plan.json

The review prints what will be created, which variables need a value, and a “Finish by hand” list with the MIOSA command for each item. Every field that cannot be carried appears there; nothing is dropped silently.

If you are already logged in with the Railway CLI, --via railway-cli reads through that session, so no token passes through MIOSA at all.

What maps

RailwayMIOSA
ProjectProject
Environment (production)the project’s default Deploy target; others with --include-targets
ServiceWeb service, Worker, Private service or Schedule
Repository, branch, root directory, Dockerfilethe same
Start commandthe service’s start command
Replicasreplicas (1 to 50)
Regionthe nearest MIOSA region (us-east, us-west, us-mia)
Cron schedulea Schedule service
Variablesvariable names, and ${{Service.VAR}} references unchanged
Postgres, Redis, MySQL pluginsmanaged databases
Custom domainlisted, and added when the Services domain API is live; until then the exact command is printed

MIOSA uses the same ${{Service.NAME}} reference syntax as Railway, so a reference keeps working when the service names match.

What does not map

RailwayWhat to do
Build commandMIOSA builds from the Dockerfile or detects the stack; move it into the Dockerfile or a build script
Health check path and timeoutMIOSA checks readiness on the service’s port
Restart policyMIOSA restarts a failed release by policy
Sleep on idle, watch patterns, pre-deploy commandno MIOSA equivalent yet; the review lists each one
Service volumesthe Services API has no service volumes yet; the review prints miosa volume create and where to attach it
*.up.railway.app domainsRailway-owned DNS; MIOSA assigns <label>.<org>.miosa.app
MongoDB pluginsno managed MongoDB
Other environmentsreported unless --include-targets, which copies names, not values

Rotate the secrets

The import refuses to run while a variable has no value, which is deliberate: it should not start a service with a leaked secret.

  1. Assume every secret in the Railway project is compromised.
  2. Do not copy values during the import; set new ones on MIOSA.
  3. Rotate at the provider first (database password, Stripe, SMTP, API keys), then paste the new value into MIOSA.
  4. Deploy after the new values are set, so the service never starts with a leaked one.
  5. Delete the old Railway project once the MIOSA one serves traffic.
miosa service vars set <project> <service> STRIPE_KEY=<new value>
miosa service deploy <project> <service>

--copy-variables exists for a test project where nothing is sensitive. It prints the same warning, and it is never the default.

If you only have a config file

railway.json and railway.toml are Railway’s config-as-code. --from-file reads one locally, with no token and no network call:

miosa import railway --from-file ./railway.toml --service-name api

The plan has no source, because a config file has none; pass --repo or --image when you apply, or edit the plan.

Undo

Every id the apply creates is recorded as it is created.

miosa import railway --cleanup --state .miosa/import-railway-acme-api.json --yes

--cleanup prints exactly what it will delete first, and deletes only those ids. An interrupted apply continues with --resume.

Railway terms, MIOSA names

Railway saysMIOSA calls it
ProjectProject
EnvironmentDeploy target
ServiceService (Web service, Worker, Private service, Schedule)
PluginDatabase
DeployRelease
VariablesVariables
VolumeVolume (machine-scoped today)
DomainPublic address or custom domain
Was this page helpful?