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.
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.
-
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.sqlThe 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.
-
Pin an exact version number in
docker-compose.yml, neverlatest. Withlatest, a simpledocker compose pullcan move you to a new major version without you deciding it. -
Go up one major version only: for example, replace
dolibarr/dolibarr:20.0.4withdolibarr/dolibarr:21.0.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 -
Restart and read the log until “Migration successful”:
docker compose pull web && docker compose up -d && docker compose logs -f web -
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.phpis 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_IDis set randomly when the container is created. In the code, if the variable is empty, the fixed valuemyinstanceuniquekeyis 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.