Skip to content

Move an application to another cluster

If you redeploy somewhere else, copy the data by hand and change the address, every step can lose writes or traffic, and none of it can be undone once you are halfway.

In Flui, a move is one operation. Flui builds the new copy beside the old one, keeps it in step, and swaps the two only when you say so, or as soon as the copy is ready if you asked for that. Until the swap, nothing on the source has changed and you can cancel the whole move.

Three kinds of move

There are three commands because each kind of move carries different risks.

You want to moveCommandWhat crosses
An applicationflui migrate appThe running copy, its volumes, and its address
A Flui-managed PostgreSQL databaseflui migrate dbThe data
An application and the database it readsflui migrate fullBoth, in one cutover

migrate app moves the application and the files it keeps. The application keeps its name and address, and its volumes are copied to the new cluster. At cutover its address is repointed there. A database it reads is not included: use migrate db for that, or migrate full for both.

migrate db moves only the data. The destination database comes up with its own new address, name, user and password, and the source is left read-only so nothing writes to it by accident. It does not change what your application connects to. migrate full does that for you.

The shape of every move

Every move has four steps, and only the last two are permanent.

  1. Create. The destination is built and kept in step with the source. The source keeps serving.
  2. Cut over. Traffic, writes, or both move to the destination. --cutover auto does this as soon as the destination is ready. --cutover manual pauses and waits for you.
  3. Abort. Available at any point before the cutover completes. The destination is removed and the source is left untouched.
  4. Destroy the source. Optional, and only after the move has completed. It gives the old cluster its capacity back, and cannot be undone.

Steps 2 and 4 are separate so you can check the result first. After a completed move the old copy stays in place, drained. It still uses capacity, and you can inspect it until you destroy it.

What can move, and what cannot

Flui refuses a move it cannot finish when you ask for it, not halfway through. The refusal says which rule you hit. One exception: a full move checks the last two rules below only when its application starts to move, after the database has been copied.

An application can move when:

  • it runs somewhere other than the target cluster;
  • it does not read values out of another application’s secrets (move the applications it depends on first);
  • it answers at a real domain. An application still on the fallback address built from its cluster’s IP cannot move, because that address belongs to the old cluster. Assign a DNS zone first: see Put an application on the internet;
  • if it has volumes, there is a backup destination for them to travel through: the one protecting the application, your only one, or the one you name with --backup-destination. See Protect and restore.

Only one migration can run at a time for each application.

migrate full has one more rule. Flui has to see, in the application’s own configuration, that it points at the database you named: either the standard PGHOST setting matching the source database, or a postgres:// URL that matches it. An application that reads its connection from a linked secret cannot be rewired, so the move is refused instead of guessed at.

Start the move

Terminal window
# an application, with its volumes
flui migrate app create my-api --to production-eu
# a managed database
flui migrate db create orders-db --to production-eu
# both together
flui migrate full create my-api --db orders-db --to production-eu

--to (-t) is required and takes a cluster name or id. --from (-f) names the source cluster. You can leave it off when the profile has only one cluster; name it when there are more. You can give the application by name or by id.

--cutover auto is the default on all three. Pass --cutover manual to pause the move and start the cutover yourself.

flui migrate db has four more flags:

FlagWhat it does
--mode live|restorelive streams from the running database, then cuts over. restore rebuilds from its backups instead, for when the source is already gone. Default live.
--at <ISO-8601>Restore mode only: the point in time to recover to.
--nameA display name for the new database. Defaults to the old one’s slug with -migrated on the end.
--no-verifySkips the per-table row-count comparison Flui makes at cutover. The check is on by default, and a mismatch stops the cutover.

flui migrate full has --staging, which decides how the new copy of the application waits:

  • scaled-down (the default): the copy is built but not started until cutover. This is safe, and the write pause at cutover is a little longer.
  • live-fenced: the copy is started at full size against the destination database while that database is still read-only, so it is already warm at the swap. This shortens the pause, but your application has to meet two conditions. It must start and pass its health check without writing anything, and it must not run background work with effects outside the database (queue consumers, scheduled jobs, outgoing mail or webhooks) until cutover. Flui holds back only its database writes.

Watch it

A move takes time. These are the states it goes through:

  • pending, then provisioning: the destination is being built. A full move shows db_replicating and then app_staging instead.
  • replicating: a database move is streaming. A database restored from backups goes through restoring instead, straight to completed.
  • ready (an application or a full move) or synced (a database): the paused state. Under auto the move passes straight through it. Under manual it waits here for you.
  • cutover, then completed.
  • failed, or aborted.

A full move has one more state, failed_forward: it failed after the database was promoted, which is the one point that is never rolled back. You can recover by running the cutover again.

Terminal window
flui migrate app list
flui migrate db show <migration-id>
flui migrate full list --json

Each of the three kinds has its own list and show. list prints the id, the state, the target cluster and the cutover mode. show adds what is moving and where (for an application, the route between clusters) and the error, if there was one. Both take --json.

Cut over

With --cutover auto there is nothing to do here. The move cuts over as soon as the destination is ready.

With manual, the move waits at ready (or synced for a database) until you start the cutover, so you choose when it happens.

What the cutover does depends on the kind:

  • An application is repointed at the destination cluster and its address follows it. It keeps its name and identity throughout. If it has volumes, it stops while the last changes to its files are copied, then starts on the destination. The move tells you this when you start it, and records how long the pause lasted. If the copy fails, the application starts again on its original cluster.
  • A database stops taking writes, has its row counts checked table by table against the copy unless you turned that off, and the copy is promoted. From then on the source is read-only and stays that way.
  • A full move does both together, and rewrites the application’s connection settings to the new database in the same step.
Terminal window
flui migrate app cutover <migration-id>
flui migrate db cutover <migration-id>
flui migrate full cutover <migration-id>

Use the command that matches the kind of move. You cannot drive the parts of a full move on their own. Flui refuses and tells you to drive the full move instead.

Abort a move

An abort removes what was built on the destination and leaves the source as it was. Nothing has moved yet, so there is nothing to move back.

You can abort from pending up to the paused state, and also from failed, so a broken move can be cleaned up. The dashboard offers abort up to the paused state. To abort a failed move, use the CLI (flui migrate app abort, and the same for db and full) or an agent.

Abort is no longer available once the swap has happened. For a full move the limit comes earlier: once the database has been promoted, abort is refused. After that point there is no consistent old state to return to, so you finish the cutover instead. The refusal tells you so.

When you abort a database move, Flui drops the replication link but leaves the half-built destination database in place for you to inspect. You have to remove it yourself.

Destroy the source

After a move completes, the old copy is still there, drained. It receives no traffic, but it still uses the old cluster’s capacity.

destroy-source frees that capacity. It works only on a completed move, it touches only the copy on the source cluster, and it cannot be undone.

A database move has no destroy step. The source database is left read-only, not removed. To remove it, remove it as an application, which also deletes its storage. See Remove an application and its data.

Terminal window
flui migrate app destroy-source <migration-id>
flui migrate full destroy-source <migration-id>

Both ask you to confirm. --yes (-y) skips the question; use it only in scripts.

What an agent may do with this

There are three permission groups, from lowest to highest:

GroupWhat it reaches
See migrationsmigration_list, migration_get. Changes nothing.
Run migrationsThe above, plus migrate_app, migrate_db, migrate_full and migration_cutover, and reading the applications and clusters involved.
Abort and tear down migrationsThe above, plus migration_abort and migration_destroy_source.

A group is granted or refused as a whole, so what you agreed to is exactly what the key carries. Choose what your agent may do explains how to choose one.

Every step that changes something on the destination or removes something on the source stops and asks a person. You approve it in the dashboard, and the agent finds out by trying again. When your agent stops to ask you explains why it works that way.

Where to go next