How to Migrate from Railway to AWS ECS in Under a Day
Migrating from Railway to AWS ECS sounds like a week-long project. It doesn't have to be. If your app is already containerised — or close to it — the actual migration is mostly configuration and a database copy. The average Railway-to-ECS migration with NoahOps takes 4–8 hours.
This guide covers the full process: what you need before you start, the migration steps in order, the parts that trip people up, and what to watch after cutover. No Terraform. No YAML manifests. Just the actual steps.
What You're Moving and Where It Goes
Before starting, map what you have on Railway to what you'll have on AWS:
| Railway | AWS equivalent | |---|---| | Railway service (container) | ECS Fargate task | | Railway Postgres | AWS RDS PostgreSQL | | Railway Redis | AWS ElastiCache Redis | | Railway environment variables | ECS task env vars + Secrets Manager | | Railway custom domain | ALB + ACM certificate | | Railway deploy on push | ECR + ECS rolling deploy via GitHub Actions |
The core conceptual shift: Railway manages the infrastructure for you on Railway's cloud. On AWS via NoahOps, the same infrastructure (VPC, ECS cluster, RDS, Redis, ALB) lives in your own AWS account. NoahOps provisions and manages it — but you own it.
Prerequisites
Before you start, make sure you have:
- An AWS account (if you don't have one, create one at aws.amazon.com — the free tier is enough to start)
- Your Railway app's GitHub repo with a working Dockerfile (or a Dockerfile you can write in ~30 minutes)
- Railway CLI installed and authenticated (
railway login) - AWS CLI installed (
brew install awsclion Mac, or the installer on Linux/Windows)
That's it. You don't need Terraform, CDK, or any infrastructure-as-code tooling if you're using NoahOps.
Step 1: Export Your Railway Postgres Database (10 minutes)
Do this first. Your database is the most important thing to get right, and you want to run this export while your Railway app is still the source of truth.
Get your Railway database connection string from the Railway dashboard: your service → Variables → DATABASE_URL.
Run pg_dump:
pg_dump "postgresql://postgres:password@containers-us-west-123.railway.app:5432/railway" \
--no-acl --no-owner \
-f railway-backup.sql
Verify the dump file exists and has a non-zero size:
ls -lh railway-backup.sql
If your database is large (>1GB), add --jobs=4 for parallel export. Keep the backup file — you'll use it in Step 4.
Step 2: Make Sure You Have a Dockerfile (15 minutes)
If your Railway app already has a Dockerfile, skip to Step 3.
If Railway was auto-detecting your runtime with Nixpacks, you need a Dockerfile. Here are working starting points for common stacks:
Node.js:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "src/index.js"]
Python (FastAPI/Flask):
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Go:
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN go build -o main .
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/main .
EXPOSE 8080
CMD ["./main"]
Test your Dockerfile locally before proceeding:
docker build -t my-app .
docker run -p 3000:3000 my-app
If it runs locally, it'll run on ECS.
Step 3: Connect Your AWS Account to NoahOps (10 minutes)
In the NoahOps dashboard:
- Go to Integrations → Cloud Connect → AWS
- NoahOps will give you a CloudFormation stack link — click it and it opens in your AWS console
- Deploy the stack (takes ~2 minutes) — this creates a scoped IAM role that NoahOps uses to provision resources in your account
- Copy the role ARN back into NoahOps and click Connect
NoahOps now has permission to create VPCs, ECS clusters, RDS instances, and related resources in your account. It creates nothing until you ask it to.
Step 4: Provision Your AWS Environment (20 minutes, mostly waiting)
Tell NoahOps what you need. Using Noah AI, describe your setup in plain English:
"Set up a production environment with a Node.js ECS Fargate service, a PostgreSQL RDS database, and a Redis ElastiCache cluster. Use a t3.medium for RDS and cache.t3.micro for Redis."
NoahOps provisions:
- A dedicated VPC with public and private subnets
- An ECS cluster with Fargate
- An RDS PostgreSQL instance (multi-AZ optional — recommended for production)
- An ElastiCache Redis cluster
- An Application Load Balancer with HTTPS (you'll add your certificate in Step 6)
- Security groups that allow only the traffic that should flow between each component
This takes 10–15 minutes. RDS provisioning is the slowest part.
While it's provisioning, move to the database import.
Step 5: Import Your Database to RDS (15 minutes)
Once RDS is ready, NoahOps shows you the RDS endpoint in the environment dashboard.
Connect and import your backup:
# NoahOps gives you the connection string — it looks like this:
psql "postgresql://app_user:generated-password@your-rds-instance.abc123.us-east-1.rds.amazonaws.com:5432/appdb" \
< railway-backup.sql
Verify the import:
psql "postgresql://app_user:generated-password@your-rds-instance..." \
-c "\dt" # list tables
You should see all your tables. Spot-check a few row counts against Railway:
# On Railway
railway run psql -c "SELECT COUNT(*) FROM users;"
# On RDS
psql "postgresql://..." -c "SELECT COUNT(*) FROM users;"
If the counts match, your data is clean.
Step 6: Deploy Your App to ECS (15 minutes)
Connect your GitHub repo in NoahOps: Services → New Service → Connect GitHub repo.
NoahOps automatically:
- Builds your Docker image on each push to
main - Pushes it to ECR (your private container registry in your AWS account)
- Deploys to ECS with a rolling update (zero downtime)
- Rolls back automatically if the health check fails
Set your environment variables in NoahOps under Services → Environment Variables. Copy them from Railway's dashboard. Replace:
DATABASE_URL→ your RDS connection string (NoahOps injects this automatically if you link the service to the RDS instance)REDIS_URL→ your ElastiCache endpoint (same — NoahOps links automatically)
For secrets (API keys, third-party credentials), NoahOps stores them in AWS Secrets Manager and injects them as environment variables at runtime. Your secrets never touch the NoahOps platform — they go directly to your AWS account.
Trigger your first deploy by pushing to main or clicking Deploy in the dashboard. Watch the ECS task come up in the deployment log.
Once the health check passes, your app is running on AWS.
Step 7: Verify Everything Works (20 minutes)
Before touching DNS, verify the new environment completely.
NoahOps gives you a temporary .noahops.app subdomain for your ALB. Hit it and smoke test your app:
# Test your API
curl https://your-app.noahops.app/health
# Test a real endpoint
curl -H "Authorization: Bearer test-token" \
https://your-app.noahops.app/api/users
Things to check:
- Health endpoint returns 200
- Database reads work (hit an endpoint that queries your DB)
- Database writes work (create a test record)
- Redis works if you're using it for sessions or caching
- Background jobs / workers are running if applicable
- Logs appear in CloudWatch (NoahOps links you directly from the dashboard)
Run this in parallel with Railway still live. You're not committing to anything until you change DNS.
Step 8: Cut Over DNS (5 minutes)
When you're satisfied the new environment is working:
- In your DNS provider, update your domain's A record (or CNAME) to point to your AWS ALB endpoint
- If you haven't already, add your domain to NoahOps → Services → Custom Domain — it provisions an ACM certificate and configures HTTPS automatically
- Set TTL to 60 seconds before cutover so the change propagates quickly
Monitor your Railway and AWS environments simultaneously for 15–30 minutes after cutover. Traffic should shift to AWS as DNS propagates.
Step 9: Decommission Railway (after 24–48 hours)
Wait 24–48 hours with both environments running before shutting down Railway. This gives time to catch any edge cases you didn't test.
Once you're confident, delete your Railway services and database. Your infrastructure now lives entirely in your own AWS account — not on Railway's platform, not on NoahOps' platform. It's yours.
The Parts That Actually Trip People Up
Database connection pooling. RDS doesn't have built-in connection pooling the way Railway's Postgres does. If your app opens many concurrent connections (common in Node.js), add PgBouncer or use RDS Proxy. NoahOps can provision RDS Proxy for you — just ask Noah AI.
Health check path. ECS requires a health check endpoint. If your app doesn't have one, add a simple /health route that returns 200 before deploying. ECS will mark your task unhealthy and keep rolling back without it.
Environment variable differences. RDS connection strings look different from Railway's. DATABASE_URL on Railway might use a different SSL mode than RDS requires. Add ?sslmode=require to your RDS connection string if you're getting SSL errors.
Redis connection. ElastiCache Redis doesn't have a public endpoint — it's inside your VPC. Your ECS tasks connect to it over the private subnet. This is correct security behaviour, but it means you can't connect to it from your local machine directly. Use AWS Session Manager or a bastion host for debugging.
What You Get That Railway Couldn't Give You
Once you're on AWS:
- Your own AWS account — infrastructure belongs to you, zero vendor lock-in
- VPC isolation — production, staging, and preview environments in separate private networks
- RDS with automated backups — point-in-time recovery, multi-AZ failover
- SSH/ECS Exec access —
aws ecs execute-commandlets you open a shell in any running container - SOC 2 / compliance ready — documented VPC controls, audit logging via CloudTrail, encryption at rest
- Auto-rollback on deploy failure — ECS rolls back automatically if your new task fails its health check
- Slack notifications — NoahOps sends deploy success/failure notifications to your Slack channel
FAQs
Do I need to know AWS to do this migration? Not if you're using NoahOps. Noah AI handles the infrastructure configuration. You describe what you need in plain English, it provisions the resources. You interact with NoahOps, not directly with the AWS console.
What if my app uses Railway's private networking between services? Private networking between Railway services becomes VPC-internal networking between ECS services. Services in the same VPC communicate over private IPs — NoahOps configures this automatically. Your services just need to point at each other's internal endpoints rather than Railway's internal DNS.
Can I keep a staging environment on Railway while moving production to AWS? Yes, temporarily. During the migration it's useful to have Railway as a fallback. Long-term, NoahOps provisions isolated staging and production environments in the same AWS account — staging gets its own VPC, its own RDS, and deploys from a different branch.
What does this cost compared to Railway? Early-stage (small app, low traffic): similar cost. At growth scale (10k+ daily active users, significant DB size): AWS with NoahOps is typically 30–50% cheaper than Railway, because you can use RDS Reserved Instances and right-size your Fargate tasks. Railway's usage-based model doesn't have equivalent cost optimisation levers.
What if something goes wrong during the migration? Your Railway environment stays live until you change DNS. There's no risk of data loss or downtime — the migration is entirely additive until the final DNS cutover. If something isn't working in the AWS environment, just don't change DNS and debug at your own pace.
Request a free demo at noahops.com — we'll walk through your Railway setup and give you a migration plan specific to your stack.