
We're excited to introduce new CLI commands that make it easy to migrate your existing databases from Fly.io to AWS.
If you haven't yet caught up on the details about our new AWS architecture, you can read more about Turso Cloud on AWS, and How We Built a Fully S3-Based Database Architecture.
The good news for free tier users is that there's nothing to do! We've migrated most of our free tier users to AWS already. If you're on a paid plan, you can now also move your databases to AWS, which also means you get a new price if you subscribe to the Developer Plan.
We've added three new commands to the Turso CLI to help you manage the migration process:
turso group aws-migration info <group-name> # Shows status of the group migration process
turso group aws-migration start <group-name> # Start migration process for selected group
turso group aws-migration abort <group-name> # Abort migration process for selected group
Before you begin the migration process, please note:
Migration is irreversible: Once you migrate to AWS, you cannot migrate your database back to Fly. However, you can abort the process with the turso group aws-migration abort <group-name> command if some databases weren't migrated yet.
Deprecated features: After migrating to AWS, the following features that were available on Fly will no longer work:
ATTACH between group databaseswss:// to libsql://)Python SDK compatibility: If you're using Python, ensure you have the latest libsql SDK installed: https://github.com/tursodatabase/libsql-python (use import libsql, NOT import libsql_client).
Extension support: Some lesser-known extensions (e.g., sqlite-vss, sqlite-geopoly) aren't supported on AWS. Please confirm your required extensions are available before migrating.
Embedded replicas: Embedded replicas require special care, as the client-server protocol is not the same on AWS. See Embedded replicas on AWS below before migrating.
All databases from your group will be migrated to the closest region to the group primary. During migration, databases remain online for read-only access. Writes are paused until the transfer completes—typically under a minute, but it may take longer for large databases (1 GB+).
After migration, we recommend updating the URL for all migrated databases of the group. You can get the new URL with the turso db show <db-name> command.
If you are using Embedded Replicas, there are extra steps needed once the databases are migrated.
The on-disk format of an embedded replica on AWS is not compatible with the one used on Fly. After migrating, delete the local state of the replica (replica.db, replica.db-wal, replica.db-shm, replica.db-client_wal_index, and any other replica.db-* files) and let the next connect() download the database again. You might need to restart your application for this to happen. After the database is re-synced, you can resume normal usage.
This applies whether you keep using the libSQL SDKs or switch to the new Turso Sync packages described below: in both cases, start from a fresh local state.
These apply if your embedded replica sends writes to the remote database (the default).
All SDKs
INSERT/UPDATE/DELETE) so that they don't return rows: use the SDK's execute/run method and avoid RETURNING. If you only need the generated id, consider using the last insert rowid instead. See the SDK sections below for the exact methods.TypeScript / JavaScript (@libsql/client, libsql)
@libsql/client: client.execute() picks the right path automatically, so avoiding RETURNING in writes is enough. If you need the generated id, use lastInsertRowid from the result.libsql package used directly: run writes with stmt.run() (it returns lastInsertRowid), not stmt.get(), stmt.all() or stmt.iterate().client.transaction(), client.batch() and client.migrate() short. Don't await unrelated work inside a transaction.await client.reconnect() and retry. With the libsql package directly, close the Database and open a new one.Python (libsql)
isolation_level=None (or autocommit=True on Python 3.12+). If you keep the default, call conn.commit() right after each write.cursor.execute() picks the right path automatically, so avoiding RETURNING in writes is enough. If you need the generated id, use cursor.lastrowid.conn.executescript(...).libsql.connect(...) again, and retry.conn = libsql.connect("replica.db", sync_url=url, auth_token=token, isolation_level=None)
Go (go-libsql)
db.Exec (or tx.Exec), not db.Query / db.QueryRow, even when the statement has no RETURNING. If you need the generated id, use LastInsertId() from the result.sql.Tx short-lived.db.SetMaxIdleConns(0), or a short db.SetConnMaxIdleTime.db := sql.OpenDB(connector)
db.SetMaxIdleConns(0)
If you use embedded replicas, you should consider switching to the new Turso Sync packages: @tursodatabase/sync for JavaScript/TypeScript, plus the Python and Go Turso SDKs. They don't support remote writes. Instead, you write to the local database and push() changes to the cloud. Switching also requires starting from a fresh local state.
More information, as well as a detailed comparison about the two methods of replications can be found here.
Ready to make the move? Try out the new commands today!