---
id: platform/migrate-from-railway
title: Migrate from Railway
description: Map a Railway railway.json or railway.toml service to a Bex render.yaml, recreate its Postgres and Redis references, and see which Railway settings have no Bex equivalent.
keywords: [bex, migrate, railway, railway.json, railway.toml, render.yaml, migration guide]
last_updated: 2026-10-05
---

Railway's [config as code](https://docs.railway.com/reference/config-as-code)
file, `railway.json` or `railway.toml`, configures the build and deploy of one
service. A Bex [render.yaml](./app-resource.md) describes managed services,
datastores and disks. Most of a Railway file maps, but regions, resource limits,
restart policies and environment overrides do not, and moving a file moves no
data.

Railway now marks config as code deprecated in favour of
[infrastructure as code](https://docs.railway.com/infrastructure-as-code), and
says existing files stop being read on 2026-12-01 (both checked 2026-10-01).
The converter reads the documented config-as-code keys, which are what a
repository holds today.

Bex is in active development and its upstream project does not yet recommend
production workloads. Begin with a non-production rehearsal and check
[compatibility](./compatibility.md) and required capabilities before proceeding.
Compare costs with [Railway pricing](https://railway.com/pricing) and
[Bex pricing](/pricing) directly; this guide does not restate either.

## Use the converter

Paste one service's `railway.json` or `railway.toml`, and optionally its
variables, into the [Railway converter](/tools/railway-to-render-yaml). It is a
browser starting point: it drafts a render.yaml by the rules below, lists every element it could
not map with the reason, and checks the draft with the
[render.yaml checker](/tools/render-yaml-checker). `bex blueprints validate`
remains the authoritative check. Nothing you paste leaves your browser. Convert
each service of a Railway project separately.

## How Railway maps to Bex

| Railway | Bex |
| --- | --- |
| `builder: DOCKERFILE`, or `dockerfilePath` | Docker runtime (`dockerfilePath`) |
| `RAILPACK` (the default) or `NIXPACKS` | A native runtime when `startCommand` shows one, else Docker |
| `buildCommand` | `buildCommand` (native runtimes only) |
| `startCommand` | `startCommand`, or `dockerCommand` on Docker |
| `preDeployCommand` | `preDeployCommand` |
| `healthcheckPath` | `healthCheckPath` |
| `numReplicas` | `numInstances` |
| `cronSchedule` | Cron job with `schedule` |
| No `cronSchedule` | Web service; change it to `worker` if it serves no traffic |
| `sleepApplication` | Explained: only free-tier web services sleep when idle |
| `requiredMountPath` | A disk (paid plan, single instance, choose a size) |
| Postgres or Redis reference variables | A new, empty Bex Postgres or Key Value, wired with `fromDatabase` or `fromService` |
| Other variables | One environment group; secrets become `sync: false` |
| `environments.<name>` overrides | Not mapped: listed per environment, never merged |
| `watchPatterns`, `restartPolicyType`, `region`, `multiRegionConfig`, `limitOverride`, `healthcheckTimeout` | Not mapped, with the reason |

## Example

This is the converter page's sample: a Bun service built with Railpack that
uses a Railway Postgres and Redis.

```toml
[build]
builder = "RAILPACK"

[deploy]
startCommand = "bun run src/index.ts"
healthcheckPath = "/"
restartPolicyType = "ALWAYS"
```

Its variables in `KEY=VALUE` form, as `railway variable list --kv` prints them,
with references as written in Railway:

```text
# railway variables --kv, with references left as written
DATABASE_URL=${{Postgres.DATABASE_URL}}
PGHOST=${{Postgres.PGHOST}}
PGPORT=${{Postgres.PGPORT}}
REDIS_URL=${{Redis.REDIS_URL}}
MONGO_URL=${{MongoDB.MONGO_URL}}
SHARED_FLAG=${{shared.FEATURE_FLAG}}
PUBLIC_URL=https://${{RAILWAY_PUBLIC_DOMAIN}}
RAILWAY_DOCKERFILE_PATH=Dockerfile
NODE_ENV=production
PORT=8080
STRIPE_SECRET_KEY=replace-me-do-not-commit
```

The converter drafts this render.yaml from them:

```yaml
# Draft render.yaml for Bex, converted from Railway by bex.co/tools.
# A starting point, not a migration: replace the placeholders, enter secrets
# in the dashboard, choose plans, and move data, domains and add-on
# credentials separately. Then run `bex blueprints validate`.
services:
  # From railway.toml: railway.toml
  # plan: not set; choose one at https://bex.co/pricing
  - type: web
    name: app
    runtime: docker
    repo: https://github.com/YOUR-ORG/YOUR-REPO  # replace with your repository
    branch: YOUR-BRANCH  # replace with your deploy branch
    dockerCommand: bun run src/index.ts
    healthCheckPath: /
    envVars:
      - key: DATABASE_URL
        fromDatabase:
          name: app-db
          property: connectionString
      - key: PGHOST
        fromDatabase:
          name: app-db
          property: host
      - key: PGPORT
        fromDatabase:
          name: app-db
          property: port
      - key: REDIS_URL
        fromService:
          name: app-cache
          type: keyvalue
          property: connectionString
      - fromGroup: app-config
  # From .env line 5: REDIS_URL
  # plan: not set; choose one at https://bex.co/pricing
  - type: keyvalue
    name: app-cache
    ipAllowList: []
databases:
  # From .env line 2: DATABASE_URL
  # plan: not set; choose one at https://bex.co/pricing
  - name: app-db
envVarGroups:
  # From .env: variables
  - name: app-config
    envVars:
      - key: NODE_ENV
        value: production
      - key: STRIPE_SECRET_KEY
        sync: false  # set this secret in the dashboard
```

The Postgres and Redis references become a new, empty `app-db` database and
`app-cache` Key Value store. Rename `app` to your service's name, replace the
repository and branch placeholders, choose plans, and enter `STRIPE_SECRET_KEY`
before you validate it. No native runtime can be inferred from a `bun` command,
so the draft builds a Dockerfile. The converter lists the Railpack builder, the
restart policy, the MongoDB, shared and domain references, and
`RAILWAY_DOCKERFILE_PATH` as not mapped, each with its reason.

## Builds and commands

Railpack is Railway's default builder, and `DOCKERFILE` builds a Dockerfile
([config as code](https://docs.railway.com/reference/config-as-code), checked
2026-10-01). A Railpack or Nixpacks build has no Bex equivalent, so the draft
uses a native runtime only when `startCommand` shows one; otherwise it falls
back to the Docker runtime and you add a Dockerfile
([Docker deploys](./docker-deploys.md)). `preDeployCommand` becomes Bex's
[pre-deploy command](./pre-deploy-commands.md), which runs in the new image
without the service's disk.

A Railway file does not say whether the service takes public traffic, so the
draft makes it a [web service](./web-services.md) listening on the `PORT` Bex
injects. Change it to a [background worker](./background-workers.md) if it
serves no requests.

## Cron jobs

A `cronSchedule` becomes a Bex [cron job](./cron-jobs.md) with the same
five-field `schedule`. Railway schedules in UTC
([Railway cron jobs](https://docs.railway.com/reference/cron-jobs), checked
2026-10-01); Bex sets no per-job timezone, so confirm it with your instance
operator. Bex cron jobs take no pre-deploy command, disk or instance count, so
the converter lists those keys as not mapped.

## Variables and datastores

Variables are not in the Railway file. A reference such as
`${{Postgres.DATABASE_URL}}` names another service's variable
([Railway variables](https://docs.railway.com/reference/variables), checked
2026-10-01). For Postgres and Redis references the draft provisions a new, empty
[Bex Postgres](./postgres.md) or [Key Value](./key-value.md) store and wires it
with `fromDatabase` or `fromService`; it never copies a connection string.
Other references cannot be resolved in the browser, so resolve them yourself.
Enter secret values through Bex's [secrets interface](./secrets.md).

## Volumes and disks

Railway allows one volume per service and no replicas with a volume
([Railway volumes](https://docs.railway.com/reference/volumes), checked
2026-10-01). A `requiredMountPath` becomes a
[persistent disk](./persistent-disks.md) on a paid plan with a single instance;
choose a size from 10 to 10,000 GB. The disk starts empty.

## What does not map

`sleepApplication` has no render.yaml field: on Bex only free-tier web services
sleep when idle ([idle services](./idle-services.md)). Regions, resource
limits, restart policies, watch patterns and health-check timeouts have no
render.yaml setting. `environments.<name>` overrides are listed per environment
and never merged into the draft; recreate per-environment differences with Bex
[projects and environments](./projects-environments.md).

## Move data and traffic

Rehearse the data transfer and the cutover with steps 4 and 5 of
[Migrate from Render](./migrate-from-render.md); they apply to any source host.
Stop every source writer,
including cron services, before the final transfer. Add your domains through
[Bex domain setup](./custom-domains.md) and use the DNS records it returns.

See [Bex vs Railway](/compare/railway) for a product comparison.
