← Scritti

11 luglio 2026 · AWS · DevOps · Migrazione Cloud · Zero Downtime · Cloudflare

Migrare da Railway/Vercel ad AWS senza downtime

Il piano reale di una migrazione end-to-end: architettura target prima di toccare il traffico, cutover progressivo all'edge invece che via DNS, database in replica live e un rollback che si preme in un secondo.

Migrare da un PaaS ad AWS è la cosa che ti chiedono quando i costi iniziano a mordere o quando ti serve networking privato che Railway e Vercel semplicemente non ti danno. È anche il tipo di lavoro dove un errore si vede in produzione, di sabato, con gli utenti connessi. Ho guidato esattamente questa transizione per lo stack di Cato — da Railway/Vercel ad AWS con Cloudflare all’edge — e la lezione più importante è controintuitiva: il downtime non lo eviti facendo il cutover in fretta, lo eviti non facendo mai un big-bang.

Ecco il piano che uso davvero, senza fuffa da consulente.

Prima disegni il target, poi tocchi il traffico

La regola zero: l’architettura di arrivo esiste, funziona in staging e ha parità funzionale col vecchio stack prima che un solo utente venga spostato. La mappatura PaaS → AWS è quasi meccanica:

Railway/VercelAWS
Railway serviceECS Fargate
Postgres (Railway/Supabase)RDS/Aurora PostgreSQL
RedisElastiCache, oppure SQS/SNS se era solo coda
Cron jobsEventBridge Scheduler + Lambda
Env varsSecrets Manager / SSM Parameter Store
Vercel/NextS3+CloudFront o OpenNext su Lambda@Edge

Davanti a tutto, Cloudflare come edge: CDN, WAF, rate limiting, Zero Trust access. Questo layer non è un dettaglio, è la leva di cutover — ci torno tra un attimo.

Un consiglio non negoziabile: tutto in IaC. Terraform/Terragrunt dal giorno uno. La complessità operativa che AWS aggiunge (VPC, IAM, networking, on-call) la assorbi solo con automazione. Se la tua migrazione produce risorse cliccate a mano nella console, non hai migrato: hai creato debito.

Il cutover si fa all’edge, non nel DNS

Qui casca la maggior parte dei piani. La tentazione è: cambio il record DNS e ho finito. No.

Il DNS ha un TTL, e i client lo cachano. Se il tuo record di produzione ha TTL 300s, dopo il flip hai fino a 5 minuti di utenti ancora sul vecchio stack — e alcuni client (browser, resolver aziendali) cachano in modo ancora più aggressivo, oltre il TTL dichiarato. Se il tuo rollback è “rimetto il DNS indietro”, stai vincolando la tua via di fuga alla stessa latenza.

Due mosse:

  1. Abbassa il TTL a 60s (o meno) 24–48h prima del cutover. Va fatto in anticipo, altrimenti il vecchio TTL ti perseguita proprio nel momento peggiore.
  2. Meglio ancora: non usare il DNS come interruttore. Sposta la decisione a un livello TTL-indipendente. Un Cloudflare Worker che instrada per percentuale, o lo split a livello di load balancer, cambia il routing istantaneamente e in modo reversibile.

Per Cato ho usato esattamente questo pattern: Cloudflare come DNS, e un Worker all’edge che inoltrava una percentuale crescente di richieste ad AWS invece che al vecchio origine. Si parte al 5–10%, si guardano gli errori, si alza. Nell’arco di un paio di settimane [da confermare la durata esatta] il traffico è passato senza un big-bang e senza una finestra di manutenzione.

Le tre strategie in tasca, da scegliere per contesto:

  • Blue-green: validi il verde in isolamento, flippi il 100%, tieni il blu caldo e ricevente per 15–30 minuti come finestra di rollback.
  • Canary/weighted: 5–10% iniziale, osservi, alzi i pesi. È la mia default per il traffico applicativo.
  • Dual-run: vecchio e nuovo stack ricevono traffico in parallelo per giorni o settimane. Il più sicuro, il più lento, quello che uso quando il rischio è alto.

Nota AWS spesso dimenticata: un ELB non scala istantaneamente. Va scaldato/testato sotto carico prima del full cutover, altrimenti il picco te lo prendi tutto nel momento sbagliato.

Il database è il pezzo che ti fa perdere il sonno

Tutto il resto è stateless e reversibile. Il DB no. pg_dump + pg_restore va bene solo per dataset piccoli o se ti puoi permettere una finestra di manutenzione. Per lo zero-downtime la logica è un’altra: replica live + cutover breve.

Il pattern è expand-contract: aggiungi le nuove strutture, sposti l’app, e ritiri il vecchio solo dopo che il nuovo ha dimostrato stabilità — mai nello stesso deploy. Durante la transizione:

  • Replica continua verso il target con AWS DMS o logical replication/CDC, così AWS resta in sync mentre il vecchio DB serve ancora produzione.
  • Dual-write applicativo dove serve tenere entrambi allineati in scrittura.
  • Online schema change con shadow table e swap atomico per le migrazioni pesanti, per non prendere lock globali su tabelle grandi.
  • Snapshot pre-cutover immediatamente prima dello switch: è la tua macchina del tempo.

Il cutover del DB si fa quando il replica lag → 0. Quella è la finestra: pochi secondi di stop-write, swap, riapri. Non prima.

E qui aggiungo il pezzo che quasi nessuno tratta: se sei multi-tenant con RLS, l’isolamento tenant va rivalidato sul DB nuovo. Le policy RLS si comportano diversamente tra Supabase e un Postgres self-hosted/Aurora. Per questo ho scritto rlsgrid: matrici di test RLS + fuzzer cross-tenant che lanci prima e dopo il cutover per verificare che nessun tenant veda i dati di un altro. Un cutover DB “riuscito” che rompe l’isolamento è un incidente di sicurezza, non un successo.

Il rollback si progetta prima, e si preme in un secondo

Un piano di cutover senza un piano di rollback esplicito è una scommessa. Prima di spostare traffico voglio, scritti e testati:

  • Il vecchio ambiente caldo e ricevente per una finestra definita.
  • Uno snapshot DB pre-cutover per il reversal immediato.
  • Soglie predefinite di error rate e latenza (p95/p99) che triggerano il rollback — decise a mente fredda, non nel panico.
  • Una procedura one-click: flip del peso all’edge o del feature flag, non un cambio DNS.

I feature flag meritano una menzione a parte: disaccoppiano “codice deployato” da “traffico che lo usa”. Ti danno canary per tenant/utente/percentuale, kill-switch immediato senza redeploy, e cutover progressivo del backend indipendente dal DNS. Sono il meccanismo di rollback più veloce che hai.

Non spegnere il PaaS finché il nuovo stack non ha fatto un giro completo

L’osservabilità durante il dual-run non è opzionale. Confronto old vs new su error rate, latency p95/p99, throughput (CloudWatch/Prometheus). Per i cron e i job batch faccio una fase prod-shadow con DRY_RUN=1: il nuovo job gira in parallelo, con lo stesso schedule, ma non scrive/non invia — solo per verificare la parità prima del go-live. Ogni Lambda logga su Slack, in ogni ambiente, sempre: se un job fallisce alle 03:00 lo devo sapere alle 03:00.

Regola d’oro: non spegni il PaaS finché il nuovo stack non ha superato un ciclo completo, picchi e job schedulati inclusi. La parità la dimostri su una settimana reale, non su uno smoke test. Per tenere d’occhio costi e infra durante la coesistenza dei due stack uso dadaguard, un watchdog read-only su AWS+Terraform+SSM — perché il dual-run costa doppio e vuoi sapere quando fermarti.

La sequenza, in ordine

  1. IaC dell’ambiente target + parità funzionale in staging.
  2. Replica DB live verso AWS.
  3. Dual-run dietro flag/edge con canary crescente.
  4. Cutover DB in finestra breve, quando il lag → 0.
  5. Osserva la finestra di rollback.
  6. Decommissiona il PaaS.

Frontend e backend possono migrare in tempi diversi — il FE spesso prima. Non serve un big-bang unico, e anzi è proprio il big-bang il nemico.

Takeaway

Il PaaS ti compra time-to-market e zero ops; AWS ti compra controllo, networking privato e costi inferiori a volume [il delta esatto dipende dal profilo di carico — da confermare]. Ma la migrazione riesce o fallisce su tre scelte: cutover progressivo all’edge invece che via DNS, database in replica live con rollback via snapshot, e la disciplina di non spegnere il vecchio finché il nuovo non ha fatto un turno intero. Ed è anche il momento giusto per chiudere i finding di pentest — rate limiting sulle rotte auth e pubbliche, security header, isolamento tenant — perché stai già mettendo le mani sull’edge. Colpo solo, due problemi risolti.