Skip to content

Docker Volumes PR Review

Ed Mozley edited this page Nov 15, 2026 · 1 revision

Docker: every storage folder on a volume - PR review

PR #350 by vsc55 Β· Verdict: merge, with fixes Β· 5.4.0

docker-compose.yml put a volume on three of the eleven folders FreeITSM stores files in - ticket attachments, change attachments and the Files module - so on a default Docker install every docker compose up -d --build deleted the rest: documents attached to tickets, notes, assets and articles, asset imports, API log archives, emailed-report downloads, screen recordings, course content, RFP files, logos, war-room files, pictures on forms and catalogue items, and label fonts. All of them are on a volume now, under the names System -> Setup already gave in its docker-compose.override.yml, and the Dockerfile creates each folder as www-data's. User page: Installation - Docker, with the upgrade note for an existing Docker install.

⚠️ Existing Docker installs: copy the files out before updating. The update that brings this fix is itself a rebuild, so files already inside the container are not saved by it - this release stops future updates losing them. The steps are on Installation.

What was checked

Question Result
Is every folder the code stores files in on a volume? Yes: uploads, recordings, lms/content, contracts/rfp-builder/uploads, system/uploads/branding, system/uploads/label-fonts, war-room/attachments and forms/images, beside the existing tickets/attachments, change-management/attachments, uploads/files, the encryption key and the TLS folder. uploads/files keeps its own files volume, nested inside uploads
Does an install that followed Setup's advice get a second set of volumes? No - the names are the ones storagePersistenceOverrideFile() writes (system-uploads-branding, forms-images, ...), so the base file and the override name the same volume on the same path, and Compose merges them
Is a new volume writable by PHP? Yes - a new named volume copies the folder's owner from the image, so the Dockerfile now makes every one of them as www-data's. A volume on a folder the image lacks would be root's
Did Setup and D013 know every folder? No - forms/images and system/uploads/label-fonts were missing from storagePersistenceDirectories(), so neither the Setup warning nor D013 ever mentioned them. Both are listed now
logs/ Left out on purpose: the Intune sync logs are diagnostics, not data
Can the next upload folder be forgotten the same way? Not without a failing test - tests/docker-storage-volumes.php scans every uploadStoreFile(), uploadPrepareDir(), uploadPrepareWebServableDir() and move_uploaded_file() destination for a folder the list lacks
The changelog row Numbered 2932, which main already had. Fixed (fix 1)
The upgrade Not said: an install with files already in those folders loses them on the update that brings this. Said on merge (fix 2)
The label-fonts folder in git Ignored as a folder, so its guard files could never ship. Fixed (fix 3)

On the test install: vsc55's tests/docker-storage-volumes.php - 54 checks, files only (no Docker, no database): every folder in the list is on a volume in docker-compose.yml, with the name Setup gives it, and declared; each exists in the image; no upload code stores into a folder the list lacks. Without the change, 9 of them fail. docker compose config accepts the file.

What was changed on merge

  1. Changelog row renumbered 2932 -> #2946 (main's 2932 is another row); the merge fixes are #2947.
  2. The operator note: before updating a Docker install with no docker-compose.override.yml, copy the listed folders out - written into the changelog and Installation.
  3. system/uploads/label-fonts ships its guard files. .gitignore ignored the folder, so its .htaccess and web.config (deny all - fonts are served only through api/assets/label_font.php) could not be committed, against the .gitignore block's own rule for every upload folder. It now ignores the folder's contents and keeps the two guards.
  4. tests/docker-storage-volumes.php strips \r with an escape rather than a literal carriage return in the source, and has a TRAP comment: a folder named by a constant such as TENANT_LOGO_DIR or DOCUMENT_STORAGE_DIR, or built in another file, is not seen by its code scan.
  5. Afterwards, the same folder on nginx (changelog row to come): deploy/nginx/freeitsm.conf returns 404 for /system/uploads/label-fonts/, as it does for every other denied upload folder - nginx has no in-folder .htaccess - and D009 (Guarded paths) probes it. tests/web-exposure-guard.php checks both.

Merged with git merge --no-ff under the contributor's name; fixes 1-4 are in the merge commit (5785d2a66).

Decided

  • The volume names are Setup's names, so nobody who followed Setup's advice is moved to new, empty volumes.
  • No automatic migration. A rebuild cannot rescue files from the container it replaces - Docker fills a new volume from the image, not from the old container - so the only safe step is the operator's copy before updating. It is said on Installation, by System -> Setup and by D013, and belongs in the 5.4.0 release notes.

Worth knowing

  • Only Docker installs built from docker-compose.yml are affected. A manual (WAMP, LAMP, IIS, nginx) install stores these folders on the server's own disk and loses nothing on update.
  • An install that made docker-compose.override.yml from System -> Setup already has volumes for the folders that override lists, under the same names - nothing to copy for those. forms/images and system/uploads/label-fonts were never in Setup's list before 5.4.0, so copy those two if the install has pictures on forms or uploaded label fonts.
  • Do not edit docker-compose.yml to add volumes - it is tracked in git, so the next git pull refuses to run. Extra volumes go in docker-compose.override.yml.
  • tests/docker-storage-volumes.php keeps three lists in step: storagePersistenceDirectories(), the compose file and the Dockerfile. A new upload folder goes in all three.
  • D013's backup folder names can collide. D013 names each backup after the folder's last part, so uploads and contracts/rfp-builder/uploads both become ./backup/uploads, and docker compose cp then nests the second inside the first. The steps on Installation use the volume names instead, which are unique.

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Responses to feedback

Blue sky thinking

Bugs resolved

Pull requests

Links

Clone this wiki locally