Dolibarr Docker upgrade without losing migrations

With the official Docker image, going from 20 to 24 in one go leaves an incomplete database, without any error. What happens, how to check your own instance, and the right procedure, tested.

Versions concerned
Official dolibarr/dolibarr image, versions 16 to 24, with MariaDB or MySQL
What was checked
Startup script of the image (docker-run.sh) and Dolibarr's migration script (install/upgrade.php) read. Real test on 27/09/2026: install in 20.0.4, then a direct jump to 24.0.0 on one side and a version-by-version upgrade on the other, with table count. Check query tested on version 16, 18, 20, 22 and 24 instances. Latest images checked on Docker Hub on 04/10/2026.

The official Docker image dolibarr/dolibarr can upgrade the database on its own at startup. It is convenient, which is why it is tempting to change the version number in docker-compose.yml, restart, and move on.

If you skip several versions at once, the database will be incomplete, and nothing will tell you. We checked it on 27 September 2026: an instance installed in 20.0.4, moved straight to 24.0.0 by following the image documentation, reports version 24.0.0 and logs “Migration successful”. Yet it is missing 17 tables: 389 instead of 406 for the same instance upgraded one version at a time. The migrations of versions 22 and 23 were never run.

Why migrations disappear

At startup, the container compares the database version with the version of its files. If they differ, it calls Dolibarr’s migration script once, from the installed version to the target version. But that script only picks two files: the one that starts at your version and the one that ends at the target version. For an upgrade from 20 to 24, it runs the 2021 migration and the 2324 migration. Everything in between is ignored.

Dolibarr’s web installer does not have this flaw: it takes you through the steps one by one. The automatic sequence of the image is what causes the problem.

The application starts normally, you can log in, the main screens open. The failure only shows later, the day someone uses a feature that depends on a missing table: categories on invoices or orders, analytic accounting, and others. By then, nobody links it to the upgrade any more.

Check whether your database is complete

This query looks, for each version from 17 to 24, for an element that only that version’s migration creates. It changes nothing. Run it on the Dolibarr database:

SELECT m.version, m.objet AS missing FROM (
  SELECT '17' AS version, 'llx_bank_extrafields' AS objet
  UNION ALL SELECT '18', 'llx_c_invoice_subtype'
  UNION ALL SELECT '20', 'llx_product_thirdparty'
  UNION ALL SELECT '21', 'llx_bank_import'
  UNION ALL SELECT '22', 'llx_categorie_invoice'
  UNION ALL SELECT '23', 'llx_accounting_analytic_axis'
  UNION ALL SELECT '24', 'llx_ai_request_log'
) m
WHERE m.objet NOT IN (
  SELECT table_name FROM information_schema.tables WHERE table_schema = DATABASE()
)
UNION ALL
SELECT '19', 'llx_c_tva.entity' FROM DUAL WHERE NOT EXISTS (
  SELECT 1 FROM information_schema.columns
  WHERE table_schema = DATABASE() AND table_name = 'llx_c_tva' AND column_name = 'entity'
)
ORDER BY 1;

Only versions above yours should appear. If you are on 24 and the query returns a row, a migration was skipped. It was tested on normally installed version 16, 18, 20, 22 and 24 instances: each one only reports the versions above it.

The query assumes the default table prefix, llx_. If you use another one, adapt the names.

The right procedure

The idea fits in one sentence: one major version at a time, with your own backup before you start. The service names below (web, mariadb) are those of the official example; adapt them to yours.

  1. Back up yourself. The database, and the two persistent folders: documents and external modules.

    docker compose exec -T mariadb sh -c 'mariadb-dump -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"' > backup-before-upgrade.sql

    The image does copy the database before each migration, but always under the same name, and it is overwritten at the next step: after two steps, the copy of your starting version is gone.

  2. Pin an exact version number in docker-compose.yml, never latest. With latest, a simple docker compose pull can move you to a new major version without you deciding it.

  3. Go up one major version only: for example, replace dolibarr/dolibarr:20.0.4 with dolibarr/dolibarr:21.0.4.

  4. Delete the install lock, otherwise the database will not be upgraded and Dolibarr will stay stuck on a locked installation page:

    docker compose exec web rm -f /var/www/documents/install.lock
  5. Restart and read the log until “Migration successful”:

    docker compose pull web && docker compose up -d && docker compose logs -f web
  6. Repeat for the next version, up to the target version.

Latest official images of each branch as of 4 October 2026: 16.0.5, 17.0.4, 18.0.9, 19.0.4, 20.0.4, 21.0.4, 22.0.5, 23.0.4 and 24.0.1. 18.0.10 and 24.0.2 exist, but have no image yet. Before moving to 24, read the unalterable log trap described in Dolibarr upgrade: checklist and upgrade path.

Before each step, the free module Upgrade readiness check runs most of the checks without changing anything: it reads the code of every external module against the target version, checks PHP, the database, computed fields and ODT templates, then gives you a report. It does not replace the backup or the test on a copy.

If a migration fails

The image then automatically restores the database as it was, but the container stays on the files of the new version. Dolibarr ends up stuck on the installation page. Put the previous version number back in docker-compose.yml, restart, then read the migration_error.html file in the documents folder to understand what went wrong.

Two other traps of the image

  • conf.php is not kept. It lives in the container, not in a volume, and it is rewritten from the environment variables every time the container is recreated, so at every version change. Any line added by hand disappears. Pass your settings through environment variables, or mount your own file.
  • The instance key is not random. The image documentation says DOLI_INSTANCE_UNIQUE_ID is set randomly when the container is created. In the code, if the variable is empty, the fixed value myinstanceuniquekey is written, the same for everyone. For a new installation, set your own value from the first start. On an existing instance, do not change it: this key is used to encrypt some stored settings, which would become unreadable.

If you are setting up a new instance, two more flaws of the image affect you (passwords stored as MD5, scheduled jobs that never run): see Install Dolibarr with Docker Compose.

With PostgreSQL, the image does no automatic upgrade at all: use the web installer, at your Dolibarr address followed by /install/, again one version at a time.

If your database has already skipped versions

Do not rerun the migrations by hand on the live instance. The safe way is to start again from the backup taken before the upgrade and redo the upgrade one version at a time. Without a backup, the database can be completed, but that is work to do on a copy, table by table, checking the data.

The €90 diagnosis tells you where your database stands. The Safe upgrade package rehearses the whole upgrade on a copy of your instance, in Docker precisely, before touching the real one.

See also

DolibarrDockerupgradedocker composeinstall.lockmigration