$npx -y skills add butterbase-ai/butterbase-skills --skill migrationsUse when moving a Butterbase app between regions, checking migration progress, aborting a stuck move, reverse-rolling a completed move, or tearing down retained source replicas after a move is stable
| 1 | # Butterbase App Migrations (Multi-Region) |
| 2 | |
| 3 | Moving an app between regions is an orchestrated migration: data dumped from source, restored in dest, blobs copied, runtime brought up, then traffic flipped. Source replica is retained for safe rollback until you tear it down. |
| 4 | |
| 5 | Four small tools wrap the orchestration: |
| 6 | |
| 7 | | Tool | Purpose | |
| 8 | |---|---| |
| 9 | | `list_regions` | Discover what regions exist before suggesting one. | |
| 10 | | `move_app` | Start a migration. Returns a `migration_id`. | |
| 11 | | `move_app_status` | Read the current step + replica state of a specific migration. | |
| 12 | | `manage_migrations` | Operational ops: `get_active`, `abort`, `reverse`, `list_source_replicas`. | |
| 13 | | `teardown_source_replica` | Decommission the retained source replica once you're confident. | |
| 14 | |
| 15 | --- |
| 16 | |
| 17 | ## 1. The step sequence |
| 18 | |
| 19 | A successful move walks through 11 steps in order: |
| 20 | |
| 21 | ``` |
| 22 | requested → reserving_dest → blocking_writes → dumping_data → |
| 23 | restoring_data → copying_blobs → copying_runtime → flipping_routing → |
| 24 | setting_up_reverse_replication → unblocking_writes → completed |
| 25 | ``` |
| 26 | |
| 27 | The cutover happens at `flipping_routing`. Before that, the app is still served from the source. After that, traffic is on the destination. |
| 28 | |
| 29 | Terminal states: `completed`, `aborted`, `failed`. |
| 30 | |
| 31 | --- |
| 32 | |
| 33 | ## 2. Start a move |
| 34 | |
| 35 | ``` |
| 36 | list_regions → confirm dest region is supported |
| 37 | move_app(app_id, dest_region) → { migration_id, status: "queued" } |
| 38 | ``` |
| 39 | |
| 40 | Then poll with `move_app_status({ app_id, migration_id })` every few seconds (or call `manage_migrations({ action: "get_active", app_id })` if you've forgotten the id). |
| 41 | |
| 42 | --- |
| 43 | |
| 44 | ## 3. Recovery: pick the right escape hatch |
| 45 | |
| 46 | Stuck or wrong choice? Different state, different tool: |
| 47 | |
| 48 | - **Before `flipping_routing`** (still in dump / restore / copy phases): use `manage_migrations` `action: "abort"`. Cancels cleanly; no data flipped. |
| 49 | - **After `flipping_routing`** (already cut over): abort is locked. Use `manage_migrations` `action: "reverse"`. This rolls back to source — works only while the source replica is still retained (i.e. you haven't called `teardown_source_replica` yet). |
| 50 | - **Move is done and stable** (the new region is serving traffic without issues for a while): use `teardown_source_replica({ migration_id })`. Stops paying for source-region storage. After this, `reverse` is no longer available. |
| 51 | |
| 52 | --- |
| 53 | |
| 54 | ## 4. Inspecting in-flight + retained state |
| 55 | |
| 56 | - `manage_migrations({ action: "get_active", app_id })` → `{ migration: AppMigration | null }`. Tells you if a move is in progress right now. |
| 57 | - `manage_migrations({ action: "list_source_replicas" })` → all retained replicas the caller owns. Call this before any teardown so you know what you'd lose. |
| 58 | |
| 59 | --- |
| 60 | |
| 61 | ## 5. Common pitfalls |
| 62 | |
| 63 | - **Calling `move_app` again while one is active** — backend returns 409 `ineligible`. Resolve by waiting for the active migration to terminate, or aborting it first. |
| 64 | - **Trying to abort after cutover** — returns 409. The path forward is `reverse`, not abort. |
| 65 | - **Tearing down a replica too early** — once it's gone, reverse-move is no longer possible. Verify the destination region has been serving traffic cleanly before teardown. |
| 66 | - **Forgetting `dest_region` is validated** — invalid slugs return 400. Always `list_regions` first if you're not sure. |
| 67 | |
| 68 | --- |
| 69 | |
| 70 | ## 6. What this skill does NOT cover |
| 71 | |
| 72 | - Schema migrations (table/column changes) — use `butterbase-skills:schema-design`. |
| 73 | - Region selection at app *creation* time — use `init_app` directly with the `region` parameter. |
| 74 | - Cross-region replica reads (none — Butterbase is single-region-active per app). |