agentleFS
ravidsrkravidsrk/agent-skills/skills/fly-to-aws-migration/SKILL.md

fly-to-aws-migration

Skill1 starsChanged 23 days agoMITView on GitHub

End-to-end playbook for migrating a Fly.io project to AWS — handles Fly Postgres → Aurora Serverless v2, Fly Machines → ECS Fargate, Fly static sites → S3+CloudFront, secrets migration via Secrets Manager, DNS cutover via Cloudflare. Use when the user says "migrate from Fly to AWS", "move my Fly app to AWS", "leave Fly", "AWS migration", "ECS migration", "switch to AWS", or pastes a Fly project structure (apps/, fly.toml files) and asks to move it. Covers 7 phases (audit → foundation → code prep → secrets → API cutover → static sites → cache layer), with rollback paths preserved at every step. Battle-tested on a production migration (2026) — migrated API + 2 static sites + 87-table Postgres with 9 min total downtime.

  • Reads credentials

The file

We split this skill into the rules it gives an agent

7 rules. About 0 are common, 1 is distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Read references/phases.md for the detailed step-by-step.

    line 47
  2. Distinctive:

    Ask the user upfront:

    line 51
  3. Very specific:

    Read references/phases.md for the phase you're on

    line 140
  4. Very specific:

    Read references/gotchas.md ENTIRELY before starting — most traps cost 30-60 min to diagnose

    line 141
  5. Very specific:

    Use templates/ as starting points, not copy-paste-final

    line 142
  6. Very specific:

    After each phase, verify per references/phases.md before moving on

    line 143
  7. Very specific:

    After cutover, run scripts/verify-parity.sh for at least 24h before destroying Fly resources

    line 144

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Fly.io → AWS Migration Playbook

This skill turns "I want to leave Fly for AWS" into a 7-phase, ~6-hour migration that ships in 5 PRs with every state-bearing component verified for parity and full rollback preserved.

When to use

User says any of:

  • "migrate from Fly to AWS" / "move to AWS" / "leave Fly"
  • "AWS migration" / "ECS migration"
  • Pastes a Fly project structure (apps/api/fly.toml, apps/web/fly.toml, etc.) and asks to host on AWS
  • Has $X K of AWS credits and wants to use them
  • Mentions cost concerns about Fly Machines pricing
  • Wants Fly Postgres → managed AWS DB (Aurora / RDS)

Do NOT use this skill for:

  • AWS → Fly (reverse direction)
  • Single-process Fly apps that need only a single Lambda or static site (overkill)
  • Apps with hard real-time / sub-5ms latency requirements (AWS adds ~10ms vs Fly's edge)

Core philosophy: phased + reversible

🔴 Never destroy a Fly resource until its AWS replacement has served real production traffic for ≥24h.

🔴 Every phase must be independently mergeable and rollback-able. If the user pauses for a week between Phase 3 and Phase 4, nothing breaks.

🟢 DNS is the cutover point, not infrastructure. Build everything in parallel, flip DNS last.

The 7 phases

PhaseGoalPRDuration
0Audit current Fly setupnone15 min
1Foundation IaC (VPC, IAM, Aurora, ECR, ALB)PR #160-90 min
2Code prep (Dockerfile fixes, ECR push workflow)PR #230 min
3Secrets + DB schema migrationPR #330-60 min
4+5API production cutover (Fly off, AWS on)PR #430 min downtime ≤9 min
6Static sites cutoverPR #5zero downtime
7Perf tuning (Cloudflare cache layer)PR #6optional

Read references/phases.md for the detailed step-by-step. Read references/gotchas.md BEFORE running any phase — there are at least 12 traps that cost real time on a production migration.

Critical inputs to collect before starting

Ask the user upfront:

  1. 🔴 AWS account ID + which region they want (ap-southeast-1 for SEA, us-east-1 for US, eu-west-1 for EU)
  2. 🔴 Cloudflare zone ID (where DNS lives) + global API key OR API token
  3. 🔴 List of Fly apps to migrate — flyctl apps list
  4. 🔴 Database size — flyctl postgres connect -a <db> -c "SELECT pg_size_pretty(pg_database_size(current_database()))"
  5. 🔴 Maintenance window — when can we tolerate ~10 min API downtime?
  6. 🟡 Available AWS credits — informs Aurora ACU sizing and ECS task count
  7. 🟡 Is apps/api/Dockerfile set to listen on process.env.PORT or a hardcoded port? (Phase 2 fix needed if hardcoded)

Workflow at a glance

Phase 0  → flyctl apps list, flyctl machines list, fly pg connect, du -sh state
Phase 1  → terraform apply foundation (VPC, IAM, Aurora, ECR, ALB, ACM)
            └─ outputs: aurora endpoint, ECR URI, ALB DNS, github_deploy role ARN
Phase 2  → fix Dockerfile if needed, add ECR push workflow, push :latest
Phase 3  → pg_dump from Fly → restore to Aurora (in maintenance window OR with replication)
            └─ migrate flyctl secrets → AWS Secrets Manager (8 secret groups by category)
Phase 4+5 → flyctl scale count 0 on Fly app, ECS service update --force-new-deployment,
            wait healthy, flip Cloudflare CNAME api.* → ALB
Phase 6  → S3 + CloudFront for static sites (web + docs)
            └─ npm build → aws s3 sync → flip DNS
Phase 7  → Cloudflare Tiered Cache + cache rule for HTML (4.8x TTFB win, $0/mo)

Decision matrix: which AWS services per Fly component

Fly componentAWS replacementReasoning
🟢 Fly Machines (Node/Bun API)ECS FargateClosest to "container, no servers". Lambda only if request rate <1/s.
🟢 Fly PostgresAurora Serverless v2Auto-scaling, ACU range 0.5-4. Use RDS Postgres for fixed sizing >1k req/s.
🟢 Fly static sitesS3 + CloudFront + CloudflareSingle ACM cert in us-east-1, CloudFront function for URI rewrite
🟢 Fly secretsAWS Secrets Manager (grouped)Group secrets by category (db, llm, social, email, etc.) — fewer secrets = less cost
🟢 Fly volumesEFS or S3EFS for write-heavy, S3 for read-mostly or backups
🟢 Fly logsCloudWatch Logs30-day retention default
🟢 Fly internal networkingVPC with private subnets + NATDon't put services in public subnets
🟢 Fly CronEventBridge Scheduler → ECS RunTaskOR keep singleton task in ECS with internal cron
🟡 Fly health checksALB target group health checks30s interval, 5s timeout, 2 healthy / 3 unhealthy
🔴 Fly Anycast IPsCloudflare proxied DNSAWS doesn't have anycast; rely on Cloudflare edge instead

Hard-won lessons (READ THESE)

🔴 bun run build hangs on Next.js 16 in CI. Use npm run build in CI workflows. Local dev with bun is fine.

🔴 Fumadocs + Shiki needs ≥4 GB heap. Set NODE_OPTIONS=--max-old-space-size=4096 for docs builds.

🔴 CloudFront cert MUST be in us-east-1. Even if your stack is in another region. Add a second provider alias for that one cert.

🔴 Bun + Prisma WASM crash class. Same Bun version + same Prisma version + different app code can JIT-compile into a bad state that crashes ONLY on production singleton background tasks. The /health endpoint passes; signals stop writing. See references/gotchas.md for the 5-min post-deploy DB-write check.

🔴 Cloudflare ruleset entrypoints MUST be named "default". Setting any other name forces destroy/recreate.

🔴 cloudflare_argo resource requires paid Argo subscription. Use cloudflare_tiered_cache resource for the free tiered-cache feature.

🔴 Aurora Serverless v2 starts at 0.5 ACU minimum (currently — was 0 in newer regions). $58/mo floor regardless of usage. Use provisioned instance class for low-traffic apps if cost matters more than auto-scale.

🟡 Cloudflare proxied DNS = instant propagation (no TTL wait). This is the secret to <10s cutover windows.

🟡 ECS task :latest does NOT auto-update. You need aws ecs update-service --force-new-deployment to pick up new images even with :latest tag.

🟡 Migrate role permissions cap at runtime. If migrate task uses task-execution-role only, it can't read Secrets Manager. Use task-role for runtime AWS access.

🟢 Cloudflare DNS API is faster than terraform apply for the cutover moment itself. Pre-build everything in TF, do the actual DNS flip via curl API call for atomic <1s cutover.

Reference files

  • references/phases.md — Every phase with exact commands, AWS resource counts, verification steps
  • references/gotchas.md — Every trap with symptom → root cause → fix
  • references/cost-model.md — Real pricing from a production migration (Singapore region, ~$667/mo as-built, ~$265/mo right-sized)
  • references/rollback.md — Per-phase rollback procedures (each <5 min)

Templates

  • templates/terraform/ — Reusable Terraform modules (VPC, Aurora, ECS, ALB, S3+CF static sites)
  • templates/github-workflows/ — Deploy workflows (API to ECS, static sites to S3)
  • templates/dockerfile-fixes/ — Common Fly Dockerfile → AWS Dockerfile patches

Scripts

  • scripts/audit-fly.sh — Run Phase 0 audit against a Fly account
  • scripts/db-migrate.sh — pg_dump from Fly + restore to Aurora with verification
  • scripts/secrets-migrate.sh — Map Fly secrets → grouped AWS Secrets Manager entries
  • scripts/cutover-dns.sh — Atomic DNS flip via Cloudflare API
  • scripts/verify-parity.sh — Compare Fly vs AWS production responses for drift

How to use this skill

  1. Read this SKILL.md
  2. Read references/phases.md for the phase you're on
  3. Read references/gotchas.md ENTIRELY before starting — most traps cost 30-60 min to diagnose
  4. Use templates/ as starting points, not copy-paste-final
  5. After each phase, verify per references/phases.md before moving on
  6. After cutover, run scripts/verify-parity.sh for at least 24h before destroying Fly resources

Reference: production migration scorecard

Migrated 2026-05-22. Single project, 1 API + 2 static sites + 87-table Postgres.

PhaseResourcesWall timeIssues
0inventory15 minnone
184 AWS resources (VPC, Aurora, ALB, ECR, IAM, Secrets)90 min6 VPC endpoints × 3 AZ accidentally → $171/mo (right-size later)
2Dockerfile fix + ECR push workflow30 minbun + Next.js 16 build hang (workaround: npm)
387 tables + 8 secret groups45 minflyctl pg_dump SSL cert issue (use --ignore-ssl-errors flag)
4+5API cutover with 9 min downtime30 minscheduler singleton not migrating cleanly — needed PRIMARY_MACHINE_ID env var
62 static sites (S3 + CloudFront)60 minbun build hang again, Fumadocs OOM at 2GB
7Cloudflare cache layer20 mincloudflare_argo deprecated + paid; use cloudflare_tiered_cache
Total~115 AWS resources~5 hoursAll recovered, no data loss

Production state post-migration:

  • 🟢 API: 104ms p50 TTFB (was ~500ms on Fly)
  • 🟢 Static sites: 65ms p50 TTFB (was ~300ms on Fly with CDN)
  • 🟢 Database: identical row counts, parity verified via verify-parity.sh
  • 🟡 Cost: $667/mo as-built (Fly was ~$150/mo). Right-sized to ~$265/mo possible.

Final reminder

🔴 The user's data is on Fly. Treat the migration as a backup/restore with DNS in the middle, not a "lift and shift." Always have the option to flip DNS back. Always.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.