Files
rb/od-db-backup.md
T
igodwin 5dfa62ced6 Add od-db-backup runbook + script: cold MySQL/MariaDB backup for Open Dental
Rock-solid two-part backup (data directory + OpenDentImages) using the
cold-copy method: stop the DB service, verify it stopped, copy the whole
data dir (incl InnoDB ibdata1/ib_logfile*), then always restart the
service via a finally block. Covers mysqldump supplement, scheduling,
test-restore verification, and 3-2-1 retention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 23:16:31 -07:00

7.8 KiB

Runbook: Open Dental — Rock-solid database + images backup (Windows)

Applies to: The Windows machine hosting the Open Dental MySQL/MariaDB database (the "server"). Goal: A fully consistent, restorable backup of both halves of an Open Dental practice — the database and the A-to-Z images folder — following Open Dental and MySQL/MariaDB best practice, including cleanly stopping the database during the copy and restarting it after.

A complete Open Dental backup is two things, and both must be captured together:

Component Default location Holds
MySQL/MariaDB data directory C:\mysql\data\ (contains the opendental DB) All clinical/financial data
A-to-Z / OpenDentImages folder C:\OpenDentImages\ Scanned docs, images, attachments

A database backup without the images folder (or vice-versa) is not a usable restore.

Placeholders:

Placeholder Meaning
<DB_SERVICE> Name of the MySQL/MariaDB Windows service (e.g. MySQL, MySQL57, MariaDB)
<DATA_DIR> MySQL data directory — the folder containing the opendental subfolder (commonly C:\mysql\data)
<IMAGES_DIR> A-to-Z images folder (commonly C:\OpenDentImages)
<DEST> Backup destination — separate physical disk or UNC path, ideally replicated off-site
<DB_USER> / <DB_PASSWORD> A MySQL account for mysqldump (supplemental method) — from the password manager, never committed

Why "cold copy" is the rock-solid method

Open Dental databases run on MyISAM or InnoDB. For a file-level backup to be consistent:

  • A hot copy (copying the data directory while the service runs) can capture InnoDB mid-write → corrupt, unrestorable backup. Open Dental's built-in Backup tool and most "online" file backups cannot even restore InnoDB.
  • A cold copy — stop the service so it flushes and closes cleanly, copy, restart — is consistent for both engines. This is the gold-standard local backup.

Two details that make or break a cold copy:

  1. Copy the entire data directory, not just data\opendental\. InnoDB's shared tablespace and redo logs (ibdata1, ib_logfile*) live at the root of the data directory. Copy only the opendental subfolder and an InnoDB restore will fail.
  2. Verify the service actually stopped before copying. If it won't stop, do not copy — you'd capture a live datadir.

The script below does both, and always restarts the service (even if the copy fails), so the practice is never left down.


1. Pre-flight

  • Run on the database server, in an elevated PowerShell, off-hours — the copy causes downtime; Open Dental is unavailable on every workstation while the service is stopped.
  • Make sure no one is in Open Dental (fully closed on all workstations).
  • Have a <DEST> on a different physical disk (or UNC share) with enough free space for the data directory + images.

2. Run the cold backup (primary method)

From an elevated PowerShell on the server:

irm https://gitea.ivangodwin.com/ops/rb/raw/branch/main/scripts/od-db-backup.ps1 | iex

It will:

  1. Auto-detect the <DB_SERVICE>, <DATA_DIR>, and <IMAGES_DIR> (prompting to confirm/override).
  2. Report sizes and destination free space, then confirm before doing anything.
  3. Stop <DB_SERVICE> and verify it reached Stopped — aborting the copy if it doesn't.
  4. robocopy the whole data directory to <DEST>\od-backup-<timestamp>\data, then the images to …\OpenDentImages.
  5. Restart <DB_SERVICE> in a finally block — this runs even if the copy fails or is interrupted.
  6. Write MANIFEST.txt and backup.log into the backup folder.

Manual equivalent (offline / if you can't fetch the script)

Elevated PowerShell, no one in Open Dental:

net stop <DB_SERVICE>
Get-Service <DB_SERVICE>          # confirm Status = Stopped BEFORE copying
robocopy "<DATA_DIR>"   "<DEST>\od-backup\data"           /E /COPY:DAT /R:2 /W:5
robocopy "<IMAGES_DIR>" "<DEST>\od-backup\OpenDentImages" /E /COPY:DAT /R:2 /W:5
net start <DB_SERVICE>
Get-Service <DB_SERVICE>          # confirm Status = Running

Copy <DATA_DIR> itself (the parent of opendental), not <DATA_DIR>\opendental — you need ibdata1 / ib_logfile* at the datadir root for InnoDB. If net start fails, start it immediately: the practice cannot work until the DB is back up.

3. Supplemental logical backup (mysqldump)

A cold copy is engine-perfect but physical — a portable logical dump is a valuable second line (survives binary corruption, restores to any server/version). It runs while the service is up, so schedule it separately (e.g. mid-day incremental in addition to the nightly cold copy). Slight slowness while it runs.

mysqldump -u <DB_USER> -p --single-transaction --quick --max-allowed-packet=1024M --default-character-set=utf8 --routines --events opendental > "<DEST>\opendental-<date>.sql"
  • --single-transaction gives a consistent snapshot of InnoDB without locking the practice out. (For MyISAM, that flag does not guarantee consistency — use the cold copy as the source of truth.)
  • Compress the .sql afterward; it shrinks dramatically.
  • The dump does not include the images folder — always pair it with an <IMAGES_DIR> copy.

4. Schedule it (daily minimum)

Open Dental's floor is at least one backup per day; combine an automated nightly job with an off-site copy.

Register the cold backup as a nightly Task Scheduler job (runs off-hours, as SYSTEM/admin). Because irm | iex prompts interactively, schedule a local copy of the script with the paths baked in (keep that filled-in copy in the private tier, not here), e.g.:

schtasks /Create /TN "OD Nightly Cold Backup" /TR "powershell -NoProfile -ExecutionPolicy Bypass -File C:\ops\od-db-backup-local.ps1" /SC DAILY /ST 23:30 /RU SYSTEM /RL HIGHEST

5. Verify — a backup you haven't restored is a guess

  • Test-restore to an isolated machine periodically (matching Open Dental + MySQL/MariaDB versions): net stop, rename the existing opendental folder aside, drop in the backup's data\ contents, net start, launch Open Dental, spot-check patients/images.
  • Never restore over a live production database — data loss is irreversible.
  • Confirm the nightly job is actually producing dated folders and that they leave the building (off-site / immutable copy) — ransomware that reaches the server will reach on-line backups too.

6. Retention & off-site (3-2-1)

  • 3 copies, 2 media, 1 off-site. The <DEST> disk alone is not a backup strategy.
  • Keep several daily generations plus weekly/monthly rollups; prune old od-backup-<timestamp> folders on a schedule.
  • These files contain PHI — encrypt at rest and in transit; restrict access. HIPAA applies.

Security note

Backup media and dumps hold the entire practice's PHI. Treat them as the crown jewels: encrypt the destination, lock down share permissions, and keep at least one copy off-line/immutable so ransomware can't encrypt your backups along with production. Don't store <DB_PASSWORD> in the scheduled command line — use a MySQL option file / limited account.

References