Migrate from Railway
Instructions for coding agents to migrate a Railway app to Specific, including services, secrets, databases, storage, and custom domains.
Help the user migrate their existing Railway project to Specific. The goal is to get the same app running locally and in production, with its data intact and its infrastructure defined in a specific.hcl file. Keep the application’s language, framework, and database migration tooling wherever possible.
Run commands and make code changes for the user. Explain progress in plain language, and ask only for information or access you cannot obtain yourself. Inspect the repository and available Railway configuration before asking the user to describe their stack. Keep working through deployment and verification; writing the configuration alone does not complete the migration.
If the user is migrating because of the security incident
On October 21, 2026, Railway disclosed that an unauthorized party may have accessed customer environment variables, including some decrypted contents. Railway recommended rotating secrets and private registry credentials and reviewing service logs for unusual activity.
Moving an app does not invalidate its old credentials. Help the user address this immediately, alongside the migration:
- Inventory potentially exposed credentials across Railway environments, including API keys, database passwords, cloud credentials, webhook secrets, session signing keys, and private registry credentials. Track names and rotation status, never secret values, in the migration notes.
- Replace and revoke affected credentials at the provider that issued them. Copying an existing value into Specific is not rotation. Do not wait for the migration to finish before addressing suspected exposure; explain any interruption revocation will cause.
- Review provider usage and access logs for unexpected activity. Rotating a signing key may require invalidating existing sessions. For keys that encrypt stored data, preserve the ability to decrypt that data and plan re-encryption before retiring the old key.
- Keep secrets out of chat, committed files, command output, and logs. Use secure credential entry or the Specific Dashboard when the user needs to supply a value. Use separate development credentials locally.
Do not claim that a particular user’s credentials were stolen or that moving providers resolves every consequence of the incident. Check Railway’s linked disclosure for updates if the user asks about its status.
Step 1: Inventory the existing app
Start in the user’s repository, or help them clone it. Inspect source code, package scripts, Dockerfiles, Railway configuration such as railway.json, railway.toml, or .railway/railway.ts, and deployment workflows. Check the live Railway project through available tools as well: repository configuration may not describe everything deployed.
Build a short inventory of:
- Services, their source directories, build and start commands, runtime versions, ports, health checks, and public or private endpoints.
- Background workers, queues, scheduled jobs, and pre-deploy commands.
- Databases and extensions, Redis usage, storage buckets, and persistent volume mount paths and contents.
- Environment variable names and references, distinguishing secrets, ordinary configuration, and values embedded into frontend builds. Railway documents its variable references here.
- Production and staging environments, custom domains, external integrations, OAuth callbacks, webhooks, and automatic deployments.
Record how much data needs moving and whether the app can tolerate a maintenance window. Ask about anything missing from the available configuration, especially resources in other repositories or projects. Never assume the repository contains the production database or uploaded files.
Summarize what will move, what will remain with an external provider, and any compatibility blockers. Specific offers managed Postgres, Redis-compatible caching, S3-compatible storage, persistent volumes, services, and scheduled jobs. Do not silently convert MySQL or MongoDB to Postgres. If a dependency has no direct equivalent, agree on an external provider or a separately scoped migration and continue with the independent work.
Step 2: Install Specific and read the docs
Specific defines infrastructure in code and supplies connection details through environment variables. It runs the stack locally with specific dev and deploys it with specific deploy.
If the CLI is not installed, install it. Specific supports macOS, Linux, and Windows through WSL:
curl -fsSL https://specific.dev/install.sh | sh
Initialize the repository with specific init --agent <agent>, replacing <agent> with claude, cursor, codex, or other. Preserve existing project instructions.
Read the current documentation before writing configuration:
specific docs
specific docs builds
specific docs services
specific docs secrets-config
Read the relevant resource pages too: postgres, redis, storage, volumes, crons, and domains. Use the CLI’s documentation and --help for exact syntax; do not invent migration or import commands.
Step 3: Define the infrastructure
Write specific.hcl to describe the app’s existing architecture. Use this mapping as a starting point:
| Railway resource or setting | Specific equivalent |
|---|---|
| App build | build block with a supported base, or an existing Dockerfile via dockerfile |
| Web service | service block with a public endpoint |
| Internal service | service block with a private endpoint |
| Background worker | service block without an endpoint |
| Scheduled job | cron block; check the UTC schedule and overlapping-run behavior |
| Postgres | postgres block; connect with postgres.<name>.url |
| Redis used as a cache | redis block; connect with redis.<name>.url |
| S3-compatible bucket | storage block; update the endpoint, bucket, and credentials |
| Persistent volume | volume block inside a service; use volume.<name>.path |
| Secret variable | secret block, referenced in the service’s env |
| Non-sensitive configuration | config block or a literal in env |
| Private service reference | service.<name>.private_url, with the appropriate URL scheme |
| Pre-deploy migration command | Service pre_deploy hook |
For example, a Node app with Postgres might look like this. Adapt the commands to the project’s existing scripts and package manager:
build "web" {
base = "node"
command = "pnpm run build"
}
postgres "main" {}
service "web" {
build = build.web
command = "pnpm start"
endpoint {
public = true
}
env = {
PORT = port
DATABASE_URL = postgres.main.url
}
dev {
command = "pnpm dev"
}
}
Make the app listen on 0.0.0.0 and the supplied port. Preserve monorepo working directories and build contexts. Translate custom build requirements explicitly instead of assuming Railway’s build detection carries over.
Replace Railway-generated hostnames and variable references with Specific references. Service URL references do not include a scheme: public URLs normally need https:// in production and http:// in local development. Browser code needs a public endpoint; it cannot reach a private service address.
Check NEXT_PUBLIC_*, VITE_*, and similar variables separately: changing runtime variables does not update values already compiled into browser assets. Put only public values into frontend builds. Specific build environment references have different restrictions from service environment references; consult specific docs builds.
Keep the existing ORM and schema migration history. Add the existing migration command as a pre_deploy hook when appropriate, but coordinate its first execution with the database restore so it does not create a conflicting schema. A hosting migration does not require adopting a new schema migration tool.
Run specific check after every configuration change.
Step 4: Run and test locally
Configure development secrets in the gitignored specific.local file using its secrets block, and non-sensitive overrides in its config block. Confirm credentials and data exports are excluded from Git and deployment build contexts.
Run specific dev in the background, inspect its output, and give the user the app and local admin URLs it prints. Run existing tests and exercise the important flows yourself: sign-in, database reads and writes, uploads, service-to-service calls, and background work. Use test credentials and representative test data so local runs do not send real emails, charge customers, or consume production queues.
Fix issues and let the user try the app. If they need remote access, use specific dev --tunnel. Local success is a checkpoint; production builds and data still need verification.
Step 5: Prepare the destination and rehearse the data transfer
Use the user’s existing Specific account when available. If the CLI has no account, specific login --agent can create an unclaimed account without browser interaction. Explain that it supports one free-tier project and expires after 48 hours unless claimed. If it reports Already logged in., use that account; if it reports UNCLAIMED, ownership still needs to be completed.
Before the first deployment, prepare the configuration and application so workers, cron jobs, and other side effects stay disabled until cutover. Keep public access restricted or in maintenance mode while restoring and validating data. Do not connect a second active worker fleet to production queues during testing.
Create a project with specific project new <name> and deploy with specific deploy --project <project id>. Set production secrets separately from local values; the Dashboard can collect missing values for a pending deployment. Dashboard secret changes require a deployment to take effect. Use newly issued credentials where rotation is needed.
For an unclaimed account, complete the handoff before moving live data or traffic: run specific claim --email <their email>, give the user the printed link, then run specific claim --code <code> with the code they receive after signing in. Do not leave production on an expiring account. Verify the destination plan has the capacity and features the app needs.
Postgres
Inspect the source PostgreSQL version, extensions, database size, roles, and migration history. Check compatibility with the destination before moving data. Use a source connection reachable from the machine performing the export; a Railway private hostname is not a public connection endpoint.
Plan and test a logical export and restore with PostgreSQL tools such as pg_dump and pg_restore. Select compatible tool versions, use the destination’s direct rather than pooled connection, and adapt ownership and grants to the destination’s permissions. Do not copy Railway’s database roles or passwords into the new database blindly.
Rehearse against an empty destination before the final transfer. Inspect restore errors and validate tables, row counts, constraints, sequences, extensions, and the app’s migration history. Never restore over a non-empty database without explicitly planning how to preserve or replace its contents. specific query --db <name> is read-only and cannot perform a restore.
Files, buckets, and Redis
Creating a bucket or volume does not transfer its contents. Copy existing objects or volume files using tools appropriate to the source and destination. Preserve object keys, metadata, content types, permissions, and any URL behavior the app relies on. Verify counts, sizes, and representative checksums and downloads. Plan a final synchronization after writes stop, including how to reconcile deletions.
Determine whether Redis holds disposable cache entries, sessions, or durable work. A cache can often start empty; sessions and queues need an explicit transfer or drain plan. Check compatibility with Specific’s Redis-compatible service. Do not discard queued jobs or assume Redis is only a cache.
If a resource cannot be exported or restored with the available access, explain that specific blocker. Continue preparing the rest, but do not call the data migration complete.
Step 6: Switch production traffic
Before changing live traffic, present the tested migration result, expected downtime, and rollback plan. Get the user’s agreement on the cutover window if it has not already been authorized. A maintenance window is often the simplest approach for an app with a database; do not promise zero downtime without implementing and testing a replication strategy.
- Back up the source and confirm the restore procedure works. Keep backups protected and outside the repository.
- Stop all source writes, including workers, scheduled jobs, uploads, and webhook handlers. Account for in-flight jobs and requests. Keep the old app in maintenance mode during DNS propagation so clients cannot keep writing to the old database.
- Perform the final database transfer and file synchronization. Verify the result before enabling writes at the destination.
- Start and verify the Specific deployment at its managed URL. Check health, authentication, data access, uploads, and integrations. Enable workers and scheduled jobs only when their source counterparts are stopped and their data is ready.
- Attach custom domains with
specific domain add <service> <domain>. Show the DNS records printed by the CLI and help the user apply them at their DNS provider. Update OAuth callbacks, webhook destinations, CORS settings, and other URL-dependent integrations as needed. A Railway-provided hostname cannot move with the app. - Verify DNS, HTTPS, and real application flows through the final domain. Inspect logs, error rates, queue processing, and scheduled runs. Use
specific status,specific deployment show, andspecific docs observabilityto investigate problems.
Keep the source data available for recovery during the agreed rollback period. Once the new database accepts writes, pointing DNS back to the old app can lose those writes: recovery must reconcile the new data first. Never restore revoked or potentially compromised credentials as part of a rollback.
Step 7: Finish the migration
Give the user a short report with the live URL, checks performed, data transferred, credential rotation status, and any remaining work. Distinguish a completed migration from a partial move that still depends on Railway.
Show them the Specific Dashboard and explain how to run locally with specific dev, deploy changes, and inspect their services and data. Help configure automatic GitHub deployments if wanted. Update the repository’s agent instructions with the new infrastructure and deployment workflow.
After the user accepts the migration and the rollback period ends, help remove obsolete Railway deployment workflows and configuration, revoke remaining migration credentials, and retire Railway resources with their approval. Confirm billing and retained backups before deleting databases, volumes, or projects. Remove temporary data exports according to the agreed retention plan.