XAMPP to Docker Migration

From OpenEMR Project Wiki

XAMPP is now a dead end for OpenEMR. OpenEMR 8.3.0 (release notes) requires PHP 8.3+ and MariaDB 10.6+ (or MySQL 5.7+). The most recent XAMPP release for Windows (8.2.12, published November 2023) ships PHP 8.2 and MariaDB 10.4 — below both minimums. Apache Friends has not published a new installer since, meaning the bundled Apache, phpMyAdmin, and OpenSSL are also years behind on security fixes. There is no supported path to run current OpenEMR on XAMPP, and with ONC certification for OpenEMR 7 retired as of February 27, 2026, staying on an old version is not an option for certified practices either.

Beyond the version wall, XAMPP was always a development stack: permissive defaults, no separation between the web and database tiers, and no practical way to apply security updates in place. For a production system holding PHI, that is not a defensible configuration under the HIPAA Security Rule.

The official OpenEMR Docker images are what the project builds, tests, and patches. When a security advisory is published, remediation is docker compose pull && docker compose up -d — not a manual stack surgery on a years-old XAMPP install.

This guide walks through migrating an existing XAMPP-based OpenEMR installation to the official Docker deployment. Total hands-on time for a typical small-practice install is under an hour, most of it waiting on file copies.



What you need before starting

  • Your current XAMPP install location (typically C:\xampp\htdocs\openemr on Windows or /opt/lampp/htdocs/openemr on Linux)
  • The MySQL root password for your XAMPP MariaDB (or the OpenEMR database user credentials from sites/default/sqlconf.php)
  • A target machine for the Docker deployment. This can be the same machine, but a small Linux server or VPS is strongly recommended over Windows for production. A 2 vCPU / 4 GB VM is sufficient for most small practices.
  • Docker Engine and the Docker Compose plugin installed on the target (docs.docker.com/engine/install)
  • Enough disk space for a full copy of your sites/ directory plus your database dump
  • A maintenance window. Patients cannot be seen in the system during the cutover, but the export steps can be rehearsed in advance with the system live.

Important: your Docker OpenEMR version must match your XAMPP OpenEMR version for the initial migration — pin the image tag to whatever you run today, even if that’s an older 7.0.x release. Migrate first, upgrade second — never both at once. Once you’re running in Docker, upgrading to 8.3.0 is a supported, documented step; on XAMPP it isn’t possible at all. Check your current version under Help → About or in version.php.

Step 1 — Export the database from XAMPP

On the XAMPP machine, open a command prompt (Windows) or terminal (Linux) and run mysqldump from the XAMPP bin directory.

Windows:

cd C:\xampp\mysql\bin
mysqldump -u root -p --single-transaction --routines --triggers --events openemr > C:\migration\openemr-db.sql

Linux:

/opt/lampp/bin/mysqldump -u root -p --single-transaction --routines --triggers --events openemr > /tmp/migration/openemr-db.sql

If your database is not named openemr, check $dbase in sites/default/sqlconf.php.

Sanity check: the resulting file should be at least several megabytes for any practice with real data. A tiny file means the dump failed (usually a credentials or database-name problem).

Step 2 — Copy the sites directory

The sites/ directory contains your uploaded documents, configuration, letter templates, and any custom forms. It is the other half of your data.

Windows: copy C:\xampp\htdocs\openemr\sites to C:\migration\sites

Linux: cp -a /opt/lampp/htdocs/openemr/sites /tmp/migration/sites

If you have made custom code modifications outside sites/ (custom modules, edited core files), inventory them now — they will need to be reapplied deliberately, and this is a good moment to reconsider whether each one is still needed.

Step 3 — Set up the Docker deployment on the target

Create a project directory and a compose file. Start from the official example in the openemr-devops repository (docker/production/docker-compose.yml), pinning the image tag to your current OpenEMR version:

mkdir -p /opt/openemr && cd /opt/openemr
# place docker-compose.yml here, edit passwords, pin the openemr image tag
docker compose up -d

Watch the logs (docker compose logs -f openemr) until initial setup completes and you can reach the login page. This confirms the stack works before you overlay your data onto it.

Step 4 — Import your database

Copy openemr-db.sql to the target machine, then load it into the database container:

cd /opt/openemr
docker compose exec -T mysql sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD" openemr' < openemr-db.sql

Step 5 — Restore your sites directory

Copy your exported sites/ to the target, then place it into the app container:

tar -czf sites.tar.gz sites/
docker compose exec -T openemr sh -c 'rm -rf /var/www/localhost/htdocs/openemr/sites && tar -xzf - -C /var/www/localhost/htdocs/openemr' < sites.tar.gz

Two edits are then needed inside the restored config:

  1. Database host. In sites/default/sqlconf.php, XAMPP used localhost; the container needs the compose service name (typically mysql). Edit $host accordingly. The database credentials must match what your compose file sets.
  2. Permissions. Reset ownership so the web server can read/write:
docker compose exec openemr chown -R apache:apache /var/www/localhost/htdocs/openemr/sites
docker compose restart openemr

Step 6 — Verify

Log in and check, in order:

  • A recent patient chart opens and demographics are correct
  • A recently uploaded document opens (proves sites/documents survived)
  • Administration → Other → Logs shows no new errors
  • Billing/claims screens load if you use them
  • Any interfaces (labs, HIE feeds, clearinghouse SFTP) reconnect — these often have IP allowlists on the far end that need updating for the new host

Step 7 — Decommission XAMPP

Do not leave the old XAMPP install running “just in case” — a forgotten, unpatched, PHI-bearing web server is exactly the risk this migration removes. Stop the XAMPP services, take one final archival copy of the machine, and then take it off the network. Update any DNS entries or bookmarks to point at the new deployment.

Step 8 — Set up backups (required)

The legacy Administration → System → Backup feature was removed in OpenEMR 8.3.0 (#13471) — its import path was itself the subject of a security advisory — so your Docker deployment needs an external backup job from day one. Use the companion script from the OpenEMR repository (contrib/util/openemr-docker-backup.sh). Download it to the Docker host (not inside the container) and lock down its permissions:

curl -o /opt/openemr/openemr-docker-backup.sh https://raw.githubusercontent.com/openemr/openemr/master/contrib/util/openemr-docker-backup.sh
chmod 700 /opt/openemr/openemr-docker-backup.sh

Review the configuration section at the top of the script before the first run. The script it dumps the database with --single-transaction, archives the full OpenEMR webroot (sites/ plus custom modules and local changes — the same scope as the removed feature), verifies both archives, records image versions, and applies retention. Note that in the official images only sites/ persists across container recreation — the rest of the webroot is replaced on every upgrade, so the backup protects that state for disaster recovery, but modules installed outside sites/ must be reinstalled after upgrades (or baked into a custom image layer or bind mount). Then schedule it: via cron on Linux hosts, or — on Windows/Docker Desktop, where cron doesn’t exist — via Windows Task Scheduler invoking the script through wsl.exe (exact task settings are documented in the script’s header). Pair it with offsite replication (rclone, restic, or borg) — a backup that only exists on the same machine does not survive the ransomware scenario it exists for.

Then test a restore to a scratch VM. The script includes the full restore procedure in its footer. A quarterly restore drill also satisfies the HIPAA contingency-plan testing requirement (45 CFR 164.308(a)(7)).



Common questions

“XAMPP works fine, why change?” Because it can no longer run current OpenEMR. OpenEMR 8.3.0 requires PHP 8.3; XAMPP for Windows tops out at PHP 8.2 and has not seen a release since late 2023. Staying means freezing on an old OpenEMR version with an aging, unpatched stack in front of patient data — and no ONC-certified version available to you.

“Can’t I just replace the PHP folder inside XAMPP with PHP 8.3?” It won’t get you there. Even with PHP 8.3 grafted in, XAMPP’s bundled MariaDB 10.4 is below the 10.6 minimum, and OpenEMR 8.3.0 also requires the redis and imagick PHP extensions, which XAMPP doesn’t bundle. You’d be hand-replacing PHP, the database server, and sourcing thread-safe Windows DLLs — a stack no one supports, with every future upgrade repeating the surgery. That is strictly harder to maintain than the Docker deployment this guide describes.

“Docker is complicated.” The migration above is one compose file and a handful of commands. Compare that honestly to what you’d do today if you had to upgrade PHP inside your XAMPP install.

“Can I run Docker on my existing Windows machine?” You can, via Docker Desktop, and it is still an improvement over XAMPP. But a small Linux VM (on-prem or a HIPAA-eligible cloud provider with a BAA) is simpler to keep patched and is what the community can best support.

“What about my SSL certificate?” The production Docker configuration can manage Let’s Encrypt certificates automatically — usually simpler than whatever manual arrangement the XAMPP install had.

If you hit trouble mid-migration, nothing in steps 1–5 modifies your XAMPP install; you can simply keep running on XAMPP and ask on the community forum with the error output.