Docs/Guides/Ingesting from DockMaster
◆ Guide

Ingesting from DockMaster.

A production-grade migration from DockMaster to BoaterOS — customers, inventory, deals, service history, photos — with a parallel run and a clean cutover.

  • Two ingestion modes: flat backup file or live ODBC
  • Full field mapping with normalization and validation
  • Parallel run with writeback so DockMaster keeps working
  • Cutover checklist and post-migration validation queries
In this guide
  1. Prerequisites
  2. Discovery phase — profile the source
  3. Field mapping
  4. Photos and attachments
  5. Parallel run with writeback
  6. Cutover
  7. Post-migration validation

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.

terminal · curl
# 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:

migrate.py · python
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:

DockMaster BoaterOS Notes
Customer.CustID contact.external_id Preserved as external_id for reverse lookups during parallel run.
Customer.Name contact.full_name Split into first/last via heuristic + manual review queue.
Customer.Phone contact.phone_numbers[0] primary=true, verified=false. TCPA consent inferred no.
Customer.Email contact.email_addresses[0] Bounce-checked async after migration completes.
Customer.Address* contact.addresses[0] Normalized via USPS/SmartyStreets.
Inventory.StockNum hull.stock_number Unique per location_id.
Inventory.HIN hull.hin Validated against USCG format. Bad HINs flagged, not dropped.
Inventory.Make/Model hull.make / hull.model Passed through taxonomy normalizer (e.g. "Grady White" → "Grady-White").
Inventory.Year hull.year Coerced to int, range 1950–currentYear+2.
Inventory.RetailPrice hull.price_usd Cents precision preserved. Negative prices rejected.
Inventory.Photos hull.photos[] Deferred to S3 sync phase — see section 4.
Deal.DealNum deal.external_id + deal.number Both written so DealerTrack links keep working.

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.

GET /v1/migrations/mig_7FK9 · 200 OK
{
  "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.

validate.sql
-- 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;
◆ Next step

Ready to leave DockMaster?

We've run this playbook dozens of times. Your migration lead will have a kickoff on your calendar within 48 hours.

Book a demo Back to docs