Install Dolibarr with Docker Compose

A complete docker-compose.yml for Dolibarr 24 and MariaDB, tested end to end. With the official example, passwords are stored as MD5 with a cleartext copy, and scheduled jobs never run: how to avoid all three.

Versions concerned
Official dolibarr/dolibarr image 24.0.1 (latest published image on 13/10/2026), MariaDB 11.4; all three traps come from the startup script shared by every version of the image
What was checked
Startup script of the image read (docker-run.sh, Dolibarr/dolibarr-docker repository on 13/10/2026), as well as Dolibarr 24.0.1 code (dol_hash, dol_verifyHash, install/step5.php, cron_run_jobs.php). Three fresh installs on 13/10/2026 with Docker 28: the official example setup, the corrected setup, then the files on this page copied as is. Checked: login, password hashes in the database, cron run, stop and restart, backup from the interface, upload of a 5 MB file before and after raising Dolibarr's limit. On 14/10/2026: User::setPassword() and admin/security.php read, then two more fresh installs (files of this page without, then with the DATABASE_PWD_ENCRYPTED line): llx_user.pass column after a password change, 'Activate' button of the Security page, login after erasing the cleartext copies.

The official dolibarr/dolibarr image installs Dolibarr by itself on first start: one docker-compose.yml file, one command, and a minute later the login screen shows up. This page gives a complete setup, tested on 13 October 2026 with Dolibarr 24.0.1 and MariaDB 11.4.

It also fixes three flaws of the automatic install that the example in the official documentation does not mention, and that we saw on a fresh install:

  • user passwords are stored as unsalted MD5, whereas a regular install protects them with password_hash;
  • scheduled jobs never run: the cron container fails every five minutes with a key error that only shows in its logs;
  • a cleartext copy of passwords stays in the database: every password changed from the interface is also stored in readable form, next to its encrypted version.

What you need

Docker with the Compose plugin: docker compose version must answer. The commands below are for Linux and macOS. The whole install lives in one folder, for example ~/dolibarr: configuration, database, documents and external modules.

mkdir -p ~/dolibarr/docker-init.d && cd ~/dolibarr

1. The docker-compose.yml file

services:
  db:
    image: mariadb:11.4
    restart: unless-stopped
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    environment:
      MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MARIADB_DATABASE: dolibarr
      MARIADB_USER: dolibarr
      MARIADB_PASSWORD: ${DB_PASSWORD}
    volumes:
      - ./data/mariadb:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  web:
    image: dolibarr/dolibarr:24.0.1
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment: &dolibarr-env
      DOLI_DB_HOST: db
      DOLI_DB_NAME: dolibarr
      DOLI_DB_USER: dolibarr
      DOLI_DB_PASSWORD: ${DB_PASSWORD}
      DOLI_URL_ROOT: ${DOLI_URL_ROOT}
      DOLI_ADMIN_LOGIN: ${ADMIN_LOGIN}
      DOLI_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
      DOLI_INSTANCE_UNIQUE_ID: ${INSTANCE_UNIQUE_ID}
      DOLI_COMPANY_NAME: ${COMPANY_NAME}
      DOLI_COMPANY_COUNTRYCODE: GB
      DOLI_ENABLE_MODULES: Cron
      DOLI_CRON_KEY: ${CRON_KEY}
      DOLI_CRON_USER: ${ADMIN_LOGIN}
      PHP_INI_DATE_TIMEZONE: Europe/London
    ports:
      - "8080:80"
    volumes:
      - ./data/documents:/var/www/documents
      - ./data/custom:/var/www/html/custom
      - ./docker-init.d:/var/www/scripts/docker-init.d

  cron:
    image: dolibarr/dolibarr:24.0.1
    restart: unless-stopped
    depends_on:
      - web
    environment:
      <<: *dolibarr-env
      DOLI_CRON: 1
    volumes:
      - ./data/documents:/var/www/documents
      - ./data/custom:/var/www/html/custom

Set DOLI_COMPANY_COUNTRYCODE to your two-letter country code (US, IE, BE…) and PHP_INI_DATE_TIMEZONE to your time zone. The choices that matter:

  • Exact versions, never latest. The official example uses dolibarr/dolibarr:latest and mariadb:latest: a plain docker compose pull can then move you to a new major version without you deciding it. On 13 October 2026 the latest published image is 24.0.1; 24.0.2 is out, but its image is not on Docker Hub yet.
  • The MariaDB health check makes Dolibarr wait until the database is ready.
  • DOLI_ENABLE_MODULES: Cron enables the scheduled jobs module during the install, before the image stores the cron key. This is the fix for the second trap, explained below. You can add other modules, comma-separated (Societe,Facture,Cron).
  • The cron service uses the same image with DOLI_CRON: 1: it runs scheduled jobs every five minutes. It reuses every variable of the web service through &dolibarr-env.
  • Data stays in the data/ folder: database, documents, external modules. Together with .env, these are the only files to back up.

2. The .env file, with real passwords

Unless you change them, the official example keeps admin/admin for Dolibarr and root for the database. This command creates the .env file with random values:

cat > .env <<EOF
DOLI_URL_ROOT=http://localhost:8080
ADMIN_LOGIN=admin
ADMIN_PASSWORD=$(openssl rand -hex 12)
DB_PASSWORD=$(openssl rand -hex 16)
DB_ROOT_PASSWORD=$(openssl rand -hex 16)
INSTANCE_UNIQUE_ID=$(openssl rand -hex 32)
CRON_KEY=$(openssl rand -hex 16)
COMPANY_NAME="My company"
EOF
chmod 600 .env

DOLI_URL_ROOT is the address you will open Dolibarr at. INSTANCE_UNIQUE_ID is used as an encryption key for some settings: without it, the image writes a fixed value, the same for everyone. Do not change it once installed, or those settings become unreadable.

3. The script that protects passwords

Create the file docker-init.d/password-hash.sql. The image runs the scripts in this folder once, at the end of the install:

cat > docker-init.d/password-hash.sql <<'EOF'
INSERT INTO llx_const (name, value, type, visible, note, entity) VALUES ('MAIN_SECURITY_HASH_ALGO', 'password_hash', 'chaine', 0, '', 0);
INSERT INTO llx_const (name, value, type, visible, note, entity) VALUES ('DATABASE_PWD_ENCRYPTED', '1', 'chaine', 0, '', 0);
EOF

Do not put comments in it: the image removes lines starting with -- by rewriting the file.

The first line fixes trap 1 (MD5 passwords), the second one trap 3 (cleartext copy), both described below.

4. Start

docker compose up -d
docker compose logs -f web

Wait for the message “You can connect to the running Dolibarr web application” (about a minute in our test), then leave the logs with Ctrl+C. Open http://localhost:8080 and log in with admin and the password given by grep ADMIN_PASSWORD .env.

5. Right after the first login: change the admin password

The admin account is created before the step 3 script runs, so its password is still MD5. Open Users & Groups, the admin record, Modify, and enter a new password. It is then stored with password_hash, and Dolibarr asks you to log in again.

ADMIN_PASSWORD is only used on the very first start: changing it later in .env does nothing, even after the containers are recreated. We checked.

Trap 1: MD5 passwords

When installing through the web wizard, Dolibarr stores MAIN_SECURITY_HASH_ALGO = password_hash (file install/step5.php). The Docker image’s automatic install skips that step: the line meant to do it is commented out in docker-run.sh. Without this setting, Dolibarr hashes every password as unsalted MD5, including the ones you change later in the interface. On our fresh install, a password changed from the user record was still 32 characters long in the database, the signature of an MD5.

The matching issue in the image repository (#14) was closed in May 2026, but image 24.0.1, installed from scratch on 13 October 2026, still has the flaw.

To find out whether your instance is affected, open Admin Tools › About Security. If the line “Algorithm for Passwords” says “Undefined (If unset: ‘md5’)”, it is.

To fix an existing instance:

  1. logged in as administrator, open Setup › Other Setup and add the constant MAIN_SECURITY_HASH_ALGO with the value password_hash;
  2. without logging out, change your own password from your user record;
  3. have every other user change their password.

Dolibarr’s security page warns that after this change, all existing passwords become invalid. In 24.0.1 this is no longer true: we checked that a user still on MD5 can log in. But their password is only converted when they change it. Hence step 3.

Trap 2: a cron that never runs

With the official example setup (DOLI_CRON_KEY without DOLI_ENABLE_MODULES), the cron container prints every five minutes:

Error: securitykey provided xxxxx... does not match securitykey in setup.

Nothing shows in Dolibarr: only docker compose logs cron reveals it. The cause: on first start, the image writes your key by updating the CRON_KEY row in the database, but that row only exists once the scheduled jobs module is enabled. The update touches no row and the key is never stored. Enabling the module afterwards is not enough: the key stays empty.

On a new install, DOLI_ENABLE_MODULES: Cron solves it: the module is enabled before the key is written. This is the setup on this page.

On an existing instance, enable the Scheduled jobs module, open its setup and paste the CRON_KEY value from your .env file into “Security key for URL to launch cron jobs”. To check right away:

docker compose exec cron /bin/su www-data -s /bin/sh -c "/var/www/scripts/cron/cron_run_jobs.php $(grep '^CRON_KEY=' .env | cut -d= -f2) admin"

The output must list the jobs with “Result of run_jobs OK”, not the key error.

Trap 3: a cleartext copy of passwords

When installing through the web wizard, Dolibarr also stores DATABASE_PWD_ENCRYPTED = 1 (same file install/step5.php): this setting forbids it to keep a cleartext copy of passwords. The image’s automatic install does not set it; docker-run.sh never mentions it. Without it, every password entered in the interface is stored twice: encrypted in pass_crypted, and in cleartext in the pass column of the llx_user table. Trap 1 does not change that: with password_hash, the encrypted version is right, but the cleartext copy is still there.

On our fresh install with only the MAIN_SECURITY_HASH_ALGO line, the admin password changed at step 5 could be read as is in the database. It also showed in the HTML source of the user record opened for editing. With the second line of the step 3 script, the column stays empty: we checked it on another fresh install.

To find out whether your instance is affected, open Admin Tools › About Security. If the line “Encrypt passwords stored in database” says “No”, it is. This command lists the accounts whose password is in cleartext:

docker compose exec -T db sh -c 'mariadb -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" "$MARIADB_DATABASE" -e "SELECT login FROM llx_user WHERE LENGTH(pass) > 0"'

To fix an existing instance:

  1. logged in as administrator, open Home › Setup › Security, Passwords tab, and click Activate next to “Encrypt passwords stored in database”. Dolibarr saves the setting and erases the cleartext copies, but only those shorter than 32 characters: it takes longer ones for old MD5 hashes and leaves them;
  2. erase the remaining ones:
docker compose exec -T db sh -c 'mariadb -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" "$MARIADB_DATABASE" -e "UPDATE llx_user SET pass = NULL WHERE LENGTH(pass) > 0 AND LENGTH(pass_crypted) > 0"'

Login only uses the encrypted version: in our test, an account whose cleartext copy was erased still logs in. So there is no need to have passwords changed. Run the check command again: it must print nothing. An account still listed has no encrypted version, and the command spares it so as not to lock it out: have that user change their password.

What else the image does

  • conf.php is rewritten every time the container is recreated, from the environment variables. Changing DOLI_URL_ROOT in .env then running docker compose up -d is enough to change the address, but any manual edit to conf.php is lost.
  • Sessions are lost on every recreation: after an image update, everyone must log in again.
  • File uploads are limited to 2 MB, even though PHP accepts 20 MB. The image does set PHP to 20 MB (its documentation wrongly says 2 MB), but Dolibarr applies its own limit, set to 2,048 KB at install time. In our test, a 5 MB file is rejected with “File size is too large or file not provided”. To raise it, open Home › Setup › Security, Files tab, and change “Maximum size for uploaded files” (in KB: 20480 for 20 MB). Above 20 MB, also add PHP_INI_UPLOAD_MAX_FILESIZE and PHP_INI_POST_MAX_SIZE to the variables.
  • The database service name must match DOLI_DB_HOST. Without that variable, the image looks for a server named mysql, and the web container waits forever.

Access from the Internet

Do not publish port 8080 as is. Put a proxy that handles HTTPS in front of Dolibarr (Caddy, Traefik, Nginx), set the https:// address in DOLI_URL_ROOT, then restart with docker compose up -d. If the proxy runs on the same machine, publish the port locally only ("127.0.0.1:8080:80") so nobody can bypass the proxy.

Backups

Backup from the interface works with this setup (Admin Tools › Backup): the file lands in data/documents/admin/backup/. On the command line, the safest method is to export the database from the MariaDB container, then copy the data/documents and data/custom folders along with the .env file:

docker compose exec -T db sh -c 'mariadb-dump -u"$MARIADB_USER" -p"$MARIADB_PASSWORD" "$MARIADB_DATABASE"' > backup.sql

The file is complete if it ends with “– Dump completed”.

Upgrading later

Change the image version one major version at a time, never straight from 20 to 24: the image would skip migrations without telling you. The full procedure, tested, is in Upgrading Dolibarr in Docker.

If you would rather have it done for you, the Installation and setup package starts from this setup.

See also

DolibarrDockerdocker composeinstallationMariaDBcronpassword