How to Migrate an Application from Railway to xCloud

Updated September 24, 2026 · 16 min read

This guide shows you how to migrate a non-WordPress application from Railway to xCloud. You will inventory the Railway project, choose xCloud’s native Git or Docker workflow, move configuration and state, recreate workers and scheduled jobs, validate the application on a temporary domain, and complete a controlled DNS cutover. It is for application owners who can access the source repository, Railway project, DNS zone, and destination server. If you are still deciding, see xCloud vs Railway first.

This process does not guarantee zero downtime. A stateful application normally needs a short write freeze and final data synchronization during cutover.

Choose the correct xCloud deployment path

Use native Git when xCloud supports the application’s runtime and serving model. Use Docker when you need the closest match to Railway’s multi-service or container-based architecture.

Railway workload Recommended xCloud path What to preserve
Supported application in one repository Deploy via Git Branch, build command, start command, web root or port, and environment values
Dockerfile-based service Verified Dockerfile or Custom Docker workflow Dockerfile, entrypoint, command, listening port, and environment values
Multi-service project Docker Compose from Git Service definitions, networks, named volumes, health checks, port mappings, and dependencies
MySQL or MariaDB xCloud server database or an external database Schema, data, users, and application connection values
PostgreSQL, MongoDB, or Redis Docker Compose or an external managed service Engine version, persistence, backup, restore, and connection values
Background worker A Compose service, Supervisor process, or Laravel queue worker Command, directory, user, process count, stop signal, and queue settings
Railway cron service xCloud site-level or server-level cron job Five-field schedule, timezone assumption, command, user, and overlap behavior

The current native Git flow covers WordPress, Laravel, custom PHP, Node.js, Lovable, and supported React, Next.js, Nuxt, and Astro projects. xCloud’s Git guide says Python, Ruby, Go, and Rust are detected but are not native application types in that flow. Use a verified Dockerfile or Docker Compose workflow for those runtimes and for workloads with custom system dependencies.

Do not treat a Railway project as one xCloud site without checking its services. A Railway project can contain several web services, workers, databases, volumes, and cron jobs that need separate destinations.

Prerequisites

  • Owner or administrator access to the Railway project and target environment.
  • Access to the application’s source repository and deployment branch.
  • The Railway CLI installed and authenticated if you will export configuration or volume files.
  • A tested backup of every database and persistent volume.
  • A destination server connected to xCloud. Docker workloads require the Docker + NGINX stack.
  • Repository access configured in xCloud for private source code.
  • A temporary xCloud staging or demo domain for validation.
  • Access to the production DNS zone.
  • A maintenance window for the final synchronization and cutover.
  • A rollback owner and a defined period during which Railway will remain available.

Step 1: Inventory the Railway project

Record every Railway service and dependency before creating the xCloud destination. In Railway, note each service’s source repository, branch, build command, start command, public and private networking, variables, volume mount paths, database engine and version, worker command, cron schedule, and custom domain.

You can export the linked Railway environment’s current configuration with Railway’s Infrastructure as Code commands:

railway login
railway link
railway config pull
railway config plan

railway config pull creates or updates .railway/railway.ts. By default, existing variable values are represented with preserve() instead of being written into the file. Do not use an export option that writes secrets into a file unless you have an approved secure handling process.

Create an inventory like this:

Component Railway setting xCloud destination
Web service Repository, branch, build and start commands, listening port Native Git site or Docker service
Worker Start command and referenced queue/database variables Supervisor, Laravel Queue, or Compose service
Cron job UTC schedule and start command Site or server cron job
Database Engine, version, size, backup and connection references Server database, external service, or Compose service
Volume Service, absolute mount path, size and file owner Compose named volume, bind mount, or external storage
Domain Hostname and current Railway DNS records xCloud site domain and new DNS records

Expected result: You have a complete component map and can account for every Railway service in the xCloud design.

Step 2: Export and classify environment variables

Export configuration values, then separate portable application settings from Railway-specific values. Railway supports service and environment selection in its variable command:

railway variable list --kv

Use Railway CLI options for the exact service and environment when the linked defaults are not the source you intend to migrate. Store the output only in an approved secret manager or other protected location. Do not commit it to the repository.

Review each value before adding it to xCloud:

  • Copy application-owned settings such as feature flags and third-party integration settings.
  • Replace database, cache, queue, and internal service URLs with their xCloud destination values.
  • Do not copy platform variables such as RAILWAY_PUBLIC_DOMAIN,RAILWAY_PRIVATE_DOMAIN, or RAILWAY_TCP_PROXY_PORT.
  • Resolve Railway variable references such as ${{SERVICE_NAME.VAR}} to the destination value.
  • Rotate or recover sealed variables from their original secret manager. Railway documents that sealed values cannot be retrieved with the API, CLI variables command, or railway run.

Expected result: You have a reviewed destination variable set with no unresolved Railway references or Railway-only networking values.

Step 3: Prepare portable database backups

Create a provider-independent export for each database. Railway volume snapshots are not a portable migration format. Railway backups restore only within the same Railway project and environment, so keep them for rollback but use the database engine’s export and restore process for xCloud.

For Railway PostgreSQL, Railway documents a logical dump through a local tunnel:

railway connect postgres --tunnel-only

In another terminal, run the command Railway shows for the tunnel, using your actual local port and source credentials:

pg_dump "postgresql://postgres:<password>@localhost:<port>/railway" \
  --format=custom \
  --no-owner \
  --file=backup.dump

You can instead use Railway’s public database URL when public networking is enabled. Do not paste the URL into tickets, documentation, or shell history shared with other people.

For MySQL, MongoDB, Redis, and other engines, follow the current export procedure for that engine and validate that the artifact can be restored to the selected target. Match the destination engine version and required extensions before cutover.

Expected result: Every database has a recent portable export plus a separate Railway backup retained for rollback.

Step 4: Export persistent files

Download every Railway volume before you switch the application. Railway mounts volumes at an absolute runtime path. Record that path because the application may depend on it, especially when a relative application path maps to a location such as /app/data.

List the selected volume’s content, then download the actual file or directory you need. For example, if the persistent directory in that selected volume is /app/data, run:

railway volume files list /
railway volume files download /app/data ./railway-data

Do not copy the example path unless it matches your volume. Select the correct Railway project, environment, service, and volume first. Confirm ownership, permissions, symlinks, and file count after export.

For an xCloud Docker site, define persistent data as a named volume, an eligible bind mount, or an external managed service. Do not rely on the container layer because xCloud’s Docker documentation warns that container-layer data can disappear when a container is recreated.

Expected result: Persistent files are available outside Railway, and the xCloud design includes an explicit durable location for each source mount.

Step 5: Create the xCloud destination

Create a temporary destination without changing production DNS. Choose one of these paths.

Option A: Deploy a supported application via Git

  1. In xCloud, select Add New Site and choose the destination server.
  2. Select Deploy Via Git.
  3. Under Deploy Any App From Git, choose the public repository, connected GitHub repository, or private repository with SSH deploy key.
  4. Confirm the detected application type and deployment branch.
  5. Select the generated staging domain.
  6. For Node.js, choose the serving mode and enter the required web root, start command, or port.
  7. Open Advanced and configure Database Management, environment-file settings, and the Build & deploy script.
  8. Review the settings and select Deploy.

Option B: Deploy a multi-service application with Docker Compose

  1. Use an xCloud server with the Docker + NGINX stack.
  2. Select New Site, choose the server, and select Custom Docker.
  3. Select Docker Compose From Git.
  4. Enter a site title and choose Demo Site for the temporary domain.
  5. Select the public, private SSH, or connected-provider repository and branch.
  6. Enter the Compose file name or path.
  7. Use Auto-Detect Ports, then select the primary service port.
  8. Map any required additional public service ports to separate domains. Do not expose database or cache ports unless the migration design explicitly requires it.
  9. Enable Environment File and add the reviewed destination values.
  10. Add any required post-deployment commands to Deployment Script, then deploy.

Expected result: xCloud builds the application and makes the web service available on a temporary HTTPS URL.

Step 6: Restore databases and persistent files

Restore state only after the destination services and storage layout exist. Create a MySQL or MariaDB database from the server’s Management → Database → Add Database screen when that matches the application. For other engines, use the Compose service or external database selected during planning.

Restore the portable database export with the target engine’s documented restore process. For the PostgreSQL custom-format dump created earlier, restore into an empty target database with the target connection string:

pg_restore \
  --dbname="postgresql://<user>:***@<target-host>:<port>/<database>" \
  --no-owner \
  backup.dump

Keep the real connection string out of shared shell history and logs. Review any restore errors, then compare schema objects, critical row counts, and representative recent records with Railway before continuing. For MySQL, MongoDB, Redis, or another engine, use that engine’s current official export and restore procedure rather than adapting the PostgreSQL commands.

Upload or copy persistent files into the mapped destination volume. Preserve the path, ownership, and permissions expected by the application.

Update the xCloud environment configuration with the new database, cache, queue, storage, and internal service addresses. For a Docker site, use the site’s Environment screen and select Update Environment after editing values.

Before enabling production jobs, create a destination backup. For a Docker site, open Backup → Previous Backups → Backup Now and wait for the backup to complete. Confirm that the backup includes the eligible named volumes or bind mounts and the site’s deployment configuration.

Expected result: The destination application reads its database and persistent files without connecting back to Railway, and a completed xCloud destination backup exists.

Step 7: Recreate workers and cron jobs

Move non-web processes explicitly because a successful web deployment does not recreate them.

For a general long-running process, open the xCloud server and go to Management → Supervisor → Add New Supervisor Process. Set the command, working directory, user, number of processes, startup and shutdown timing, and stop signal. A Docker-based application can instead keep a worker as a separate long-running Compose service.

For Laravel, open the application and use Laravel Queue → Add Queue Worker. Configure the connection, queue, maximum seconds per job, memory, and process count.

For scheduled work, use either:

  • Site dashboard →Tools → Cron Jobs → Add New Cron Job, or
  • Server dashboard →Cron Jobs → Add Cron Job.

Railway cron schedules use UTC, have a five-minute minimum interval, and skip a new run if the previous run is still active. Compare those assumptions with the xCloud schedule, server timezone, command, user, and overlap behavior before enabling the job. Keep destination workers and cron jobs paused until the restored data is ready and you are certain they will not duplicate source processing.

Expected result: Every Railway worker and cron service has one controlled destination process, with duplicate execution prevented during the migration.

Step 8: Validate on the temporary domain

Test the xCloud destination while Railway still serves production traffic. Check:

  • The home page and every critical route.
  • Login, session storage, authorization, and logout.
  • Create, read, update, and delete operations on test records.
  • File upload, download, image processing, and generated assets.
  • Database row counts and representative records.
  • Queue submission, worker completion, retries, and failure handling.
  • Cron output using Run Now only when the command is safe to execute.
  • Transactional email and webhook delivery without sending duplicate production events.
  • Redirects, canonical URLs, API callbacks, CORS, and trusted proxy behavior.
  • Application and process logs for missing variables, permissions errors, or failed health checks.

Do not enable production side effects simply to prove that a page loads. Use sandbox endpoints, test recipients, and reversible records.

Expected result: The destination passes an application-specific acceptance check without depending on Railway services that will be retired.

Step 9: Perform the final synchronization and DNS cutover

Use a short write freeze for stateful data, then switch traffic only after the final copy is verified.

  1. Lower the DNS TTL at least one current-TTL interval before the maintenance window if your DNS provider and change policy allow it. Verify the authoritative DNS response shows the lower TTL before cutover.
  2. Pause writes at the application layer or place the Railway application in maintenance or read-only mode.
  3. Allow in-flight requests and worker jobs to finish. Railway sends SIGTERM during deployment teardown, so confirm that workers handle graceful shutdown.
  4. Stop or pause Railway cron jobs and workers that could change data.
  5. Create and restore the final database export.
  6. Synchronize files changed since the rehearsal copy.
  7. Compare critical row counts, recent records, file counts, and checksums where appropriate.
  8. Start the destination workers and cron jobs.
  9. In xCloud, change the temporary site to the production domain or use the site’s Domain → Change flow, then follow the DNS values xCloud displays.
  10. Update DNS at the authoritative provider and confirm the authoritative response contains the new value.
  11. Complete the certificate step documented by the selected Git or Docker deployment flow. Do not reuse Railway’s certificate because Railway does not support exporting a custom certificate from its automatic domain flow.
  12. Test the production hostname from more than one network and monitor application, worker, database, and proxy logs.

Railway says DNS changes can take up to 72 hours to propagate worldwide. Actual cutover time depends on the previous TTL and recursive resolver caches.

Expected result: Authoritative DNS points to xCloud, the production hostname presents a valid certificate, new traffic reaches xCloud over HTTPS, new writes appear only in the destination database, and background work runs only on xCloud.

Verification checklist

  • The production hostname resolves to the xCloud destination from multiple resolvers.
  • HTTPS is valid for the production hostname and required aliases.
  • Critical routes and API endpoints return the expected result.
  • Authentication, sessions, uploads, and downloads work.
  • Destination database counts and recent records match the final source export.
  • Persistent files are present with correct ownership and permissions.
  • Workers process new jobs without duplicate consumers on Railway.
  • Cron jobs use the intended schedule and user.
  • Logs show no repeated connection, permission, or missing-variable errors.
  • External integrations use the production xCloud URL where callbacks or allowlists require it.

Troubleshooting

Symptom Likely cause Fix
The xCloud build fails The Railway-detected build behavior was not recorded, or the runtime is not supported by the chosen native Git path Compare the source build command and runtime with xCloud’s Git support. Move the workload to Dockerfile or Compose when the runtime or system dependencies require a container.
The application starts but cannot reach another service A Railway private hostname or ${{SERVICE.VAR}} reference remains in the destination configuration Replace Railway private networking values with the xCloud Compose service name, server database address, or external service endpoint.
A sealed secret is missing Railway does not return sealed values through its CLI or API Recover it from the original secret manager or rotate it, then add the replacement directly to xCloud.
Database restore fails Engine version, extension, ownership, or target permissions differ Align the target engine and extensions, restore with an authorized target user, and follow that engine’s current restore diagnostics.
Uploaded files disappear after redeployment Files were written to the container layer instead of persistent storage Map the write path to a Compose named volume, eligible bind mount, or external storage, then restore the files again.
Jobs run twice Railway and xCloud workers or cron jobs are active at the same time Pause the source jobs before enabling destination jobs. Confirm queue consumer counts and cron output.
A scheduled job runs at the wrong time Railway used UTC but the destination schedule was interpreted in another timezone Confirm the xCloud server timezone and recalculate the cron expression when needed.
The domain does not verify or HTTPS remains pending DNS still points to Railway, a proxy conflicts with validation, or propagation is incomplete Compare the authoritative DNS records with xCloud’s displayed values, remove obsolete Railway records, review proxy settings, and retry after propagation.
Some users still reach Railway Resolver caches still hold the previous DNS answer Keep Railway available but read-only during the rollback window. Monitor both platforms until the old answer expires.

Common mistakes

  • Migrating only the web service. Railway workers, cron jobs, databases, and volumes are separate components and need explicit destinations.
  • Copying every variable unchanged. Railway-provided domains, TCP proxy values, and service references do not describe the xCloud network.
  • Treating a Railway volume snapshot as an export. Railway documents that its backups restore only within the same project and environment.
  • Making the first database copy during the cutover. Rehearse the export and restore first so you can estimate the write freeze and identify version problems.
  • Exposing internal ports. Databases and caches normally stay private inside the server or Compose network.
  • Starting workers before the final sync. This can create duplicate emails, webhooks, or queue side effects.
  • Deleting Railway immediately. DNS caches and rollback needs outlive the first successful browser test.

Rollback guidance

Keep the Railway project and its backups intact until the rollback window closes. If validation fails after cutover, stop xCloud workers and cron jobs first, place the xCloud application in read-only mode, and decide which platform owns writes from that point. Restore the previous DNS records only after accounting for any data written to xCloud.

Do not run both copies as writable production systems. If xCloud accepted writes, you need a reviewed reverse synchronization plan before directing traffic back to Railway. Record the rollback time, affected records, queue state, and DNS changes.

Frequently asked questions

Can xCloud import a Railway project automatically?

No single import converts every Railway service into xCloud resources. Inventory the Railway project, then map each web service, worker, cron job, database, and volume to xCloud’s Git, Docker, database, Supervisor, or cron controls.

Should I use native Git or Docker Compose?

Use native Git for a supported application type whose build and serving model fits xCloud’s Git flow. Use Docker Compose for multiple services, unsupported native runtimes, custom system dependencies, or repository-defined networks and volumes.

Can I migrate without downtime?

Do not assume zero downtime. A stateless application may cut over quickly, but a database-backed application normally needs a short write freeze and final synchronization to prevent missing or conflicting writes.

Should I copy Railway-provided variables?

No. Replace Railway domains, private hostnames, TCP proxy values, and service references with destination-specific values. Copy only portable application settings after reviewing them.

When can I delete the Railway project?

Delete it only after DNS caches have expired, xCloud has passed production validation, backups are confirmed, and the rollback window has closed.

Railway references

If you run into any issues migrating from Railway, feel free to reach out to our support team for help.