1. Prerequisites
You'll need one of the following from the DockMaster side: a full backup file (.bak for DM Enterprise, .mdb for legacy) no older than 24 hours, or read-only ODBC credentials pointed at the production database. ODBC is preferred for anything above 10k hulls — it lets us run discovery without shipping a multi-GB file around.
From the BoaterOS side, mint an API key with scope=admin.migrations. These are separate from normal integration keys and self-destruct 30 days after the migration completes.
2. Discovery phase
Every migration starts by creating a migration resource. The discovery pass reads the source, profiles field cardinality, flags dirty data, and gives you a mapping preview — nothing is written to BoaterOS yet.
# Start a discovery pass against a DockMaster ODBC source curl https://api.boater.os/v1/migrations \ -H "Authorization: Bearer $BOATEROS_ADMIN_KEY" \ -H "Content-Type: application/json" \ -d '{ "source_type": "dockmaster_odbc", "source_config": { "host": "dm-prod.dealer.local", "database": "DMEnterprise", "readonly_user": "boateros_migrate" }, "mode": "discovery" }'
The response includes a migration_id. Poll it or subscribe to migration.progress webhooks. Our Python SDK wraps this in a single call:
from boateros import Client client = Client(api_key=os.environ["BOATEROS_ADMIN_KEY"]) mig = client.migrations.start( source_type="dockmaster_odbc", source_config={"host": "dm-prod.dealer.local", "database": "DMEnterprise"}, mode="discovery", ) client.migrations.wait(mig.id) # blocks until done report = client.migrations.report(mig.id) print(report.summary)
3. Field mapping
Discovery produces a default mapping. You can accept it, override per field, or add transforms. These are the most common overrides we see on a DockMaster migration:
4. Photos and attachments
DockMaster stores photos on the local file server, not in the database. We sync them as a separate phase against an SMB share or S3 bucket you upload them to. Expect 2–4 hours for ~10k photos at 4MB average. We deduplicate by SHA-256 and generate derivative sizes (thumb, card, gallery, hero) as we go.
Heads up. Photo metadata (caption, order, primary flag) is re-linked using StockNum. If your team edits photo order in DockMaster during the parallel run, only changes newer than the last sync win.
5. Parallel run
During parallel run, BoaterOS is the system of record for anything new, and DockMaster keeps seeing writes for anything your floor staff hasn't retrained on. Our nightly job pulls deltas from DockMaster and pushes BoaterOS changes back through the same ODBC connection. Writeback is off by default; enable it with --writeback=true when you're ready.
{
"id": "mig_7FK9RBVX2",
"source_type": "dockmaster_odbc",
"phase": "parallel_run",
"writeback": true,
"counts": {
"contacts": { "imported": 18422, "updated_24h": 63, "errors": 4 },
"hulls": { "imported": 1104, "updated_24h": 12, "errors": 0 },
"deals": { "imported": 9213, "updated_24h": 2, "errors": 1 },
"photos": { "synced": 41208, "queued": 0, "errors": 17 }
},
"last_delta_at": "2026-04-21T06:00:11Z",
"next_delta_at": "2026-04-22T06:00:00Z"
} 6. Cutover
Pick a Sunday evening. Freeze DockMaster writes (we'll coordinate with your rep), run a final delta sync with mode=cutover, flip your website feed, and disable writeback. We leave DockMaster read-only access up for 90 days in case you need a diff.
7. Validation queries
Run these the morning after cutover. Any non-zero result in the first three is a ticket; the last one is expected.
-- 1. Contacts with no phone AND no email SELECT count(*) FROM contacts WHERE phone_numbers = '[]' AND email_addresses = '[]'; -- 2. Hulls missing primary photo after sync SELECT id, stock_number FROM hulls WHERE jsonb_array_length(photos) = 0 AND status = 'available'; -- 3. Deals linked to a contact that no longer exists SELECT d.id FROM deals d LEFT JOIN contacts c ON c.id = d.contact_id WHERE c.id IS NULL; -- 4. Total HIN count should match DockMaster within 0.5% SELECT count(*) FROM hulls WHERE hin IS NOT NULL;