Upgrading GLPI on Docker looks trivial - swap the image tag and bring the container back up - but that apparent simplicity is exactly what produces unplanned downtime. Maintaining client environments, most upgrade incidents don't come from the database migration itself, but from incompatible plugins, the PHP bundled in the new image, or a mis-mapped volume that makes GLPI lose its database configuration. This guide consolidates the runbook we apply in production, with the points where things actually break.
Before touching any container: the compatibility inventory
A GLPI upgrade is not just the image. Three layers have to move together: the core, the plugins and the database schema. The classic mistake is upgrading the image only to discover, with the site down, that a critical plugin (native inventory, an SSO, a custom field) has no release for the new core. Check this first:
- Exact source and target versions - a minor jump (10.0.15 to 10.0.18) is safe; a major jump (10.x to 11.x) changes the PHP requirement and may retire plugins.
- Plugin matrix - for every installed plugin, confirm a release compatible with the target exists. A plugin with no compatible version is a business decision (disable, replace or postpone the upgrade), never a 2 a.m. surprise.
- PHP bundled in the image - the official image ships the right PHP, but if you use your own image (Alpine + php-fpm), GLPI 11 requires PHP 8.2+. A new core on old PHP gives a blank page with no clear log.
- Disk space - the dump and the volume tar can temporarily double usage. Check before, not during.
Decision matrix: minor, major or skipping versions
The type of jump defines the runbook. This is the matrix we use to size the window and the risk:
| Scenario | Example | Risk | Typical window | Main concern |
|---|---|---|---|---|
| Patch/minor | 10.0.15 → 10.0.18 | Low | 5 to 15 min | Light schema migration; plugins usually follow |
| Major | 10.0.x → 11.0.x | High | 30 to 90 min | PHP, plugins and behavior changes; stage first |
| Skipping majors | 9.5.x → 11.0.x | Very high | Plan in stages | Do NOT skip; migrate 9.5 → 10.0 → 11.0 in hops |
The common mistake here is thinking that skipping versions saves time. GLPI does not guarantee schema migration across a whole major; the db:update routine expects the structure of the immediately previous version. Skipping 9.5 straight to 11 usually stalls the migration halfway, with the database in an inconsistent state - and then rollback stops being optional.
Step by step upgrade
1. Consistent backup (database + volumes)
A backup is not the dump alone. It is the database dump plus the tar of the file volumes (uploads, documents, plugin cache). Both must be from the same instant - which is why we stop the application first, keeping only the database up for the dump:
# Stop the app, keep the database running
docker compose stop glpi-app
# Database dump (dedicated user, password via environment variable)
docker exec glpi-db sh -c 'mysqldump --single-transaction \
-u glpi -p"${MYSQL_PASSWORD}" glpi' | gzip > /backup/pre-update-$(date +%F).sql.gz
# Tar of the GLPI file volumes
tar -czf /backup/pre-update-files-$(date +%F).tar.gz \
/var/lib/docker/volumes/glpi_files/_data
--single-transaction produces a coherent dump without locking the InnoDB tables. And never put the literal password on the command line: it leaks into the shell history and into docker inspect.
2. Pin the target version in the compose file
Never use the latest tag in production. It moves the target without warning and prevents a deterministic rollback. Pin the exact version:
# docker-compose.yml
services:
glpi-app:
# From:
image: glpi/glpi:10.0.18
# To (exact target version, never :latest):
image: glpi/glpi:11.0.1
volumes:
- glpi_files:/var/glpi/files
- glpi_config:/var/glpi/config # config_db.php lives here - do NOT lose it
3. Bring up the new image
docker compose pull glpi-app
docker compose up -d glpi-app
4. Run the schema migration
The migration command was renamed between GLPI generations. Confirm which binary is available before running blind:
# GLPI 10/11 (Symfony console):
docker exec -u www-data glpi-app php bin/console db:update --no-interaction
# Older GLPI (early 10.0) used:
# docker exec -u www-data glpi-app php bin/console glpi:database:update --no-interaction
Always run as the web server user (www-data or apache), never as root. Running the migration as root creates cache files with the wrong owner, and GLPI then throws permission errors - a false "the upgrade broke it" that is just ownership.
5. Clear cache and validate
docker exec -u www-data glpi-app php bin/console cache:clear
- Open the interface and check the version under Setup > General.
- Run the built-in diagnostic under Setup > Checks (PHP, extensions and permissions).
- Confirm the critical plugins show as active, not as "needs update".
- Test a real flow: open a ticket, attach a document, trigger a notification.
What we learned operating this for clients
In maintenance, the incident that repeats the most is not the migration failing - it is the migration appearing to succeed while GLPI comes up with an empty default database. The cause is almost always the same: the volume holding config/config_db.php was not mapped, or was mapped to a different path in the new version. GLPI can't find the database configuration, assumes it's a clean install and shows the installation wizard. Someone in a hurry clicks "install" and creates a new database on top - now, on top of the downtime, there's a data loss risk. That is why, in our checklist, the first post-boot validation is not the version: it is docker exec glpi-app cat /var/glpi/config/config_db.php returning the client's real configuration. If the wizard shows up, we stop everything before any click and review the volume mapping - we never proceed through the interface.
The second pattern is OPcache serving old code after the upgrade. The image changed, the code on disk is new, but php-fpm still has the old version compiled in memory. Symptom: undefined method errors or a core version that doesn't match the image. Recreating the container (not just restarting it) fixes it, because the php-fpm process is reinitialized from scratch.
Rollback plan (rehearsed, not improvised)
A rollback is only reliable if it was thought through beforehand. With the dump and the tar from step 1, the return is deterministic:
# Tear down the new version
docker compose down
# Restore the database from the dump
gunzip < /backup/pre-update-2026-07-02.sql.gz \
| docker exec -i glpi-db sh -c 'mysql -u glpi -p"${MYSQL_PASSWORD}" glpi'
# Revert the image tag in docker-compose.yml to the previous version
# (e.g. glpi/glpi:11.0.1 back to glpi/glpi:10.0.18)
docker compose up -d
The critical point of the rollback is the schema. Once db:update altered the structure, the database is no longer compatible with the old image. That is why the rollback has to restore the dump - reverting the tag is not enough. Restoring only the image, with the schema already migrated, leaves the old GLPI reading tables of a future structure, which produces silent, hard-to-trace errors.
Doing this without unplanned downtime
If the environment is critical, the window must be rehearsed in staging with a clone of the production database. That is where you find out, without pressure, whether a plugin breaks or whether the migration takes longer than the agreed window. Upgrading straight to production without that rehearsal is betting the client's availability on luck.
If your team has no window to rehearse each upgrade, or has already been burned by an update that turned into an all-nighter, NexTool handles the maintenance and upgrades of your GLPI with a rehearsed rollback and an agreed window. Talk to us about GLPI support and maintenance.
This content was produced with the assistance of artificial intelligence and reviewed by the Nextool Solutions team.