> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cockroachlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate a Sample Database

export const InternalLink = ({version, path = "", children, ...props}) => {
  let detectedVersion = version || "stable";
  if (typeof window !== 'undefined' && !version) {
    const match = window.location.pathname.match(/\/docs\/([^/]+)/);
    if (match) {
      detectedVersion = match[1];
    }
  }
  const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
  return <a href={`/docs/${detectedVersion}/${normalizedPath}`} {...props}>
      {children}
    </a>;
};

<Note>
  **This feature is in <InternalLink version="releases" path="cockroachdb-feature-availability">preview</InternalLink>** and subject to change. To share feedback and/or issues, contact [Support](https://support.cockroachlabs.com).
</Note>

In this tutorial, you will migrate a small sample PostgreSQL database to a Cockroach Continuum cluster with the Migration Assistant, from schema conversion through data verification. Use this tutorial to familiarize yourself with the Migration Assistant's workflow before using it to migrate production data. For detailed information about every stage of the process and the options available during your actual migration, refer to <InternalLink path="migration-assistant-use">Use the Migration Assistant</InternalLink>.

The Assistant explains each conversion it makes, and you review and apply every change yourself. The sample database includes a column type, a user-defined function, and a stored procedure.

## Before you begin

* This tutorial assumes that you have a Cockroach Continuum organization. If you do not yet have one, refer to <InternalLink path="create-a-continuum-organization">Create a Continuum organization</InternalLink>.
* You also need a <InternalLink path="editions-and-add-ons#editions">Mission Critical</InternalLink> cluster as the migration target. The Assistant does not create or configure the target cluster. If you do not yet have a cluster, refer to <InternalLink path="create-a-cluster">Create a Cluster</InternalLink>.
* This tutorial requires a PostgreSQL server on version 15 to 18. The server must be reachable from CockroachDB Cloud, and you must have a connection user that can create a database on it. A server bound to `localhost` or reachable only over your VPN does not work. For more information, refer to <InternalLink path="migration-assistant-deploy#connect-your-source-and-target">Connect your source and target</InternalLink>.
* To load the sample database, you need access to the `psql` client.
* To deploy the Assistant, you need an API key for a Cloud API <InternalLink version="cockroachcloud" path="managing-access">service account</InternalLink> with the <InternalLink version="cockroachcloud" path="authorization">Cluster Admin or Cluster Operator</InternalLink> role on the target cluster.
* To reach the Assistant's web interface, your network must be on the target cluster's IP allowlist.

## Step 1. Load the sample database

Create the sample database, a miniature vehicle-sharing workload, on your PostgreSQL server.

1. Save the following as `movr_sample.sql`:

   ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   CREATE TABLE users (
       id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
       name TEXT NOT NULL,
       city TEXT NOT NULL
   );

   CREATE TABLE vehicles (
       id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
       type TEXT NOT NULL,
       city TEXT NOT NULL,
       status TEXT NOT NULL DEFAULT 'available'
   );

   CREATE TABLE rides (
       id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
       rider_id UUID NOT NULL REFERENCES users (id),
       vehicle_id UUID NOT NULL REFERENCES vehicles (id),
       city TEXT NOT NULL,
       fare MONEY,
       started_at TIMESTAMPTZ NOT NULL DEFAULT now(),
       ended_at TIMESTAMPTZ
   );

   CREATE INDEX rides_rider_idx ON rides (rider_id);

   INSERT INTO users (name, city) VALUES
       ('Carl Kimball', 'new york'),
       ('Ollie Gerbich', 'seattle'),
       ('Havvy Tarbox', 'amsterdam');

   INSERT INTO vehicles (type, city) VALUES
       ('scooter', 'new york'),
       ('bike', 'seattle'),
       ('skateboard', 'amsterdam');

   INSERT INTO rides (rider_id, vehicle_id, city, fare)
   SELECT u.id, v.id, u.city, 12.50::money
   FROM users u
   JOIN vehicles v ON v.city = u.city;

   CREATE FUNCTION ride_duration_minutes(started TIMESTAMPTZ, ended TIMESTAMPTZ)
   RETURNS NUMERIC
   LANGUAGE SQL
   AS $$
       SELECT EXTRACT(EPOCH FROM (ended - started)) / 60;
   $$;

   CREATE PROCEDURE end_ride(ride UUID)
   LANGUAGE plpgsql
   AS $$
   BEGIN
       UPDATE rides SET ended_at = now() WHERE id = ride;
       UPDATE vehicles SET status = 'available'
           WHERE id = (SELECT vehicle_id FROM rides WHERE id = ride);
   END;
   $$;
   ```

   The `fare` column uses the PostgreSQL `MONEY` type, which CockroachDB does not support, so you can see the Assistant work through a conversion decision.

2. Store your PostgreSQL server's connection string in an environment variable. Replace `<your-postgres-connection-string>` with the connection string for your server's default database:

   ```bash theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   export SOURCE_URL="<your-postgres-connection-string>"
   ```

3. Create the database and load the sample:

   ```bash theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   psql "$SOURCE_URL" -c 'CREATE DATABASE movr_sample;'
   psql "$SOURCE_URL/movr_sample" -f movr_sample.sql
   ```

   The second command reports each object as it is created:

   ```
   CREATE TABLE
   CREATE TABLE
   CREATE TABLE
   CREATE INDEX
   INSERT 0 3
   INSERT 0 3
   INSERT 0 3
   CREATE FUNCTION
   CREATE PROCEDURE
   ```

## Step 2. Deploy the Migration Assistant

The Assistant runs as its own instance alongside the target cluster. Use the following steps to create it by calling the cluster's `migration-assistant` endpoint in the Cloud API. In these steps, replace `{cluster_id}` with the cluster's ID from its overview page in the Console, and `{secret_key}` with your service account's API key.

1. Create the instance:

   ```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   curl --request POST \
     --url 'https://cockroachlabs.cloud/api/v1/clusters/{cluster_id}/migration-assistant' \
     --header 'Authorization: Bearer {secret_key}' \
     --header 'Content-Type: application/json' \
     --data '{}'
   ```

   The endpoint responds right away, but the instance is not ready yet; the response shows `PENDING`:

   ```json theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   {
     "id": "f83ef76b-0e72-4a40-bbcf-a3d6188726f5",
     "cluster_id": "c061d097-84b3-4d49-b280-2f579e4a18cb",
     "deployed_region": "us-east1",
     "status": "PENDING",
     "url": "",
     "last_error": "",
     "created_at": "2026-08-07T17:37:31.235157Z",
     "updated_at": "2026-08-07T17:37:31.235157Z",
     "username": "",
     "password": "",
     "bucket_grantee": "",
     "health": "NOT_COMPUTED"
   }
   ```

   The `id`, `cluster_id`, region, and timestamps shown are specific to your instance.

2. Check on provisioning by re-running the following call every 30 to 60 seconds. Expect it to take 10 to 20 minutes. Continue once the status reaches `RUNNING`:

   ```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   curl --request GET \
     --url 'https://cockroachlabs.cloud/api/v1/clusters/{cluster_id}/migration-assistant' \
     --header 'Authorization: Bearer {secret_key}'
   ```

   Once the instance is ready, the same call returns the address of the Assistant's web interface:

   ```json theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   {
     "id": "f83ef76b-0e72-4a40-bbcf-a3d6188726f5",
     "cluster_id": "c061d097-84b3-4d49-b280-2f579e4a18cb",
     "deployed_region": "us-east1",
     "status": "RUNNING",
     "url": "https://{assistant_host}",
     "last_error": "",
     "created_at": "2026-08-07T17:37:31.235157Z",
     "updated_at": "2026-08-07T17:40:26.406841Z",
     "username": "",
     "password": "",
     "bucket_grantee": "{bucket_grantee}",
     "health": "HEALTHY"
   }
   ```

   Record the `url`. The response contains no sign-in credentials, because access to the Assistant uses your CockroachDB Cloud account.

3. In a browser, go to the `url` from the previous step and sign in with your Cockroach Continuum organization. You must be a member of the organization that owns the target cluster. If the page does not load, confirm that your network is on the cluster's IP allowlist (in the Cloud Console, under **Networking**), and allow a few minutes for a new allowlist entry to propagate.

For the full deployment lifecycle, including the status table and troubleshooting, refer to <InternalLink path="migration-assistant-deploy">Deploy the Migration Assistant</InternalLink>.

The remaining steps run in the Assistant's web interface:

## Step 3. Connect the source

1. On the **Connect & verify** page, enter the connection string for the `movr_sample` database as the source.
2. The Assistant fills in the target connection for the cluster it is attached to. Confirm that it identifies your cluster.
3. Click **Validate connections**, and fix any reported connectivity or permission problems.
4. Click **Continue**.

## Step 4. Select what to migrate

1. On the **Tables** tab of the **Select objects** page, ensure that `users`, `vehicles`, and `rides` are selected.
2. On the **Routines** tab, ensure that `ride_duration_minutes` and `end_ride` are selected.
3. Click **Save selection**.
4. Click **Continue to Schema conversion**.

## Step 5. Convert the schema

1. The Assistant flags the `rides` table, because CockroachDB does not support the `MONEY` type on the `fare` column. Click **Ask AI to convert these** and wait for the Migration agent to return updated DDL that retypes `fare` as `DECIMAL`, along with an explanation of the change.
2. Click **Apply to Schema** to apply the updated `rides` statement.
3. Click **Continue to Generate plan**.

## Step 6. Generate the plan

1. The Assistant generates your migration plan. For the sample, the plan defers the two foreign keys until after the data load and requires no changes from you.
2. Click **Continue to Pre-migration.**

## Step 7. Prepare the target

1. Under each pre-migration step, click **Execute** to carry out the step. After executing a step, the Assistant automatically expands the next step.
2. Click **Continue to Data load**.

## Step 8. Load the data

1. Click **Execute** on each table batch and wait for `users`, `vehicles`, and `rides` to finish. The sample's nine rows load in moments. Data movement uses the same machinery as <InternalLink version="molt" path="molt-fetch">MOLT Fetch</InternalLink>.
2. Click **Continue to Routine review**.

## Step 9. Create the routines

1. Both of the sample's routines convert cleanly. Click **Create all 2 routines** to create `ride_duration_minutes` and `end_ride` on the target.
2. Click **Run tests to continue** and confirm that both routines pass.
3. Click **Continue to post-migration**.

## Step 10. Finish and verify

1. Click **Execute** under each of the required post-migration steps, which restore the sample's secondary index and deferred foreign keys.
2. Click **Execute** under the optional verification step, which uses the same machinery as <InternalLink version="molt" path="molt-verify">MOLT Verify</InternalLink>. Confirm that all three tables match the source.
3. Click **Continue to results**.

## Step 11. Review the results

Confirm that the **Results** page reports every selected object migrated and verified, and that **Objects not migrated** is empty. On a real migration, that list is your remaining work queue, and you would also re-create users, privileges, and any row-level security by hand. For more information, refer to <InternalLink path="migration-assistant-overview#postgresql-compatibility">PostgreSQL compatibility</InternalLink>.

## Step 12. Clean up

1. Save local copies of any converted DDL or generated scripts you still need. The web interface you download them from goes away with the instance.

2. Delete the Assistant instance:

   ```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   curl --request DELETE \
     --url 'https://cockroachlabs.cloud/api/v1/clusters/{cluster_id}/migration-assistant' \
     --header 'Authorization: Bearer {secret_key}'
   ```

3. Optionally, drop the sample database from your PostgreSQL server:

   ```bash theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
   psql "$SOURCE_URL" -c 'DROP DATABASE movr_sample;'
   ```

<Note>
  The Assistant's scope ends at a converted, loaded, and verified target. It does not run ongoing replication or perform application cutover. For those, refer to <InternalLink version="molt" path="molt-replicator">MOLT Replicator</InternalLink> and <InternalLink version="molt" path="migration-strategy">Migration Best Practices</InternalLink>.
</Note>

## What's next

* Get more details on each stage of the migration process from <InternalLink path="migration-assistant-use">Use the Migration Assistant</InternalLink>.
* <InternalLink path="tutorial-virtual-cluster">Create Your First Virtual Cluster</InternalLink>
