Upgrading HiLMS
Every upgrade is the same three moves: back up, put the new code in place, run
hilms:upgrade — which a Docker installation does by itself as the new container starts. The
command is idempotent, so running it twice is safe.
Back up first
Section titled “Back up first”docker compose exec app php artisan backup:run # Dockerphp artisan backup:run # manualThe archive lands on the backups disk (storage/app/backups). Copy it off the machine
before an upgrade you are unsure about, or set BACKUP_S3_ENABLED=true once and let the
nightly job do it.
Docker
Section titled “Docker”HILMS_VERSION in .env is the tag the stack runs, so an upgrade starts by moving the pin:
cd /srv/hilms$EDITOR .env # HILMS_VERSION=0.7.0, the version you move todocker compose pulldocker compose up -ddocker compose logs -f app # wait for "HiLMS is up to date"The new app container runs hilms:deploy as it starts, which finds an installed database (an
administrator exists) and runs hilms:upgrade — every step described under Manual
below, with --no-interaction --force and the caches warmed. Horizon and the scheduler are
replaced with the new image, start once the application reports healthy and pick up the new
code. If the upgrade stops, the container does not come up healthy and the log names the reason; fix it and run
docker compose exec app php artisan hilms:upgrade by hand, which is also the way to run it
again at any time. Compose
also hands .env to the containers, so the pin is what artisan about reports afterwards.
Left commented out, HILMS_VERSION takes whatever latest points at, which leaves a rollback
nothing to aim at.
The stack files themselves come out of the tag as well: when compose.yaml changed between
the two versions, git show v0.7.0:compose.yaml on your machine and copy it over before
pulling. docs/install/docker.md has the commands.
Manual
Section titled “Manual”cd /var/www/hilmsphp artisan down --render=errors::503 # optional, or let hilms:upgrade --down do it
git fetch --tagsgit checkout v0.7.0composer install --no-dev --optimize-autoloadernpm ci && npm run build
php artisan hilms:upgradesudo systemctl reload php8.5-fpm # opcache.validate_timestamps is 0hilms:upgrade checks the machine, migrates, seeds any new roles and permissions, syncs the
themes’ built-in styles and compiles every style again, gives any
language with no menu of its own a header and a footer,
links public storage, generates missing API keys, republishes the package assets, counts the
media files not yet on the disk their owner names, warms the caches, asks the workers to
restart and runs the health checks. Add --down to have it hold the maintenance page for you, and --no-optimize while
debugging.
Three of those steps change data, and one only counts:
- Seeding prunes dead permissions. A permission no policy consults any more is removed, so a role that held one loses a no-op. Nothing a role can actually do changes.
- Every style is compiled again against the theme as it is now. A theme’s built-in style
files are synced into their styles — a new file becomes a style, a changed one updates it, a
removed one takes its style with it — and a theme with no active style gets its first
built-in activated (the first that meets it, where accessibility is enforced), so a new
installation reads HiLMS WCAG as the style it draws. A value a
style holds for a token the theme no longer takes is left out of its stylesheet rather than
breaking the site;
php artisan hilms:theme:styles --checknames every such value, and the health check warns about the active style’s. - A language with no menu of its own is given one. The header gets the page chosen as the course listing and, for whoever is signed in, the page chosen for students; the footer gets the starter legal pages that exist in that language. A language somebody has already assigned a menu to anywhere is left exactly as it is, and a second run creates nothing. Build your own in Menus in the panel: a menu is assigned to the header or the footer of one language, and a menu with nothing in it draws nothing at all.
- Media files are counted, never moved. The upgrade says how many files are not on the disk
their owner names (“16 files wait to move”), because a file is served from wherever its record
says and moving them inside a starting container would keep the site down until every byte had
crossed. Moving is the administrator’s act on Settings → Media and storage, or
php artisan hilms:media:relocatefrom the command line (--dry-runshows what it would move). Lesson files are served only through signed links, so a lesson URL somebody bookmarked in an older version stops working — which is the point.
Moving media to a bucket
Section titled “Moving media to a bucket”Media moves between this server and an S3-compatible bucket from the panel, with the site running. storage.md is the full version, with what each provider needs and a checklist for afterwards; in short:
- Create the buckets and a key with the provider.
- Under Settings → Media and storage, press Set up a bucket and follow the guide: it asks
for the provider’s own fields, hands out the CORS rule for
APP_URL, and checks the bucket before it saves. New uploads go to the bucket from then on; files already uploaded keep working where they are. - Optionally, under Where your files are, press Move N files (…) to …. The files move in
the background on the
media-movesqueue, one at a time, each removed from this server only once it has arrived whole; the section shows the progress and names any file that stayed.
Moving back is the same: Stop using the bucket, then Move … back to this server. The bucket’s setup stays while files live in it, so they can be fetched back; Forget the bucket clears it once the bucket is empty.
Afterwards
Section titled “Afterwards”php artisan about --only=hilmsphp artisan health:checkBoth should be green. The panel’s Health page shows the same results without a terminal.
Content a new version asks more of
Section titled “Content a new version asks more of”A block may gain a required field between versions, and every copy of that block written
before the change is then a block missing something. hilms:upgrade counts them as its last
step and says so; nothing is repaired, because which answer an editor meant is theirs to give.
php artisan hilms:blocks:checklists every page and lesson holding one — where it is, which block, which field and why —
and exits 1 when it finds any (--json for anything reading it instead of a person). Nothing
is broken meanwhile: such a page is served exactly as it was, and only an editor who opens it
is stopped, with the block marked on the canvas and the reason on it. The Health page carries
the same count as a warning, worked out at most every half hour.
A mounted theme
Section titled “A mounted theme”hilms:upgrade relinks every theme that publishes a public/ directory and the health check
refuses to pass when the active theme does not resolve, so an upgrade that moves the contract
says so. Read theming.md before upgrading an installation that runs a theme of its own: a
new contract view is a view that theme does not have yet, and hilms:theme:check <name> lists
exactly which ones. A theme that renamed or removed a token leaves the styles laid over it
without that value: hilms:theme:styles --check says which. A production container caches compiled views, so run view:clear after
editing a mounted theme.
Languages
Section titled “Languages”hilms:upgrade seeds the language from APP_LOCALE when the table is empty, and never
touches it again. languages.md is the operator’s guide to adding a second one.
Rolling back
Section titled “Rolling back”git checkoutthe previous tag, or setHILMS_VERSIONback anddocker compose up -d(the container then runs the older version’s own start-up step).- Restore the database from the archive you made before the upgrade:
unzip 2026-09-10-01-30-00.zip -d /tmp/restoregunzip -c /tmp/restore/db-dumps/mysql-hilms.sql.gz | mysql -u hilms -p hilmsphp artisan hilms:upgradeto re-warm the caches (in Docker,docker compose restart appdoes it).
A migration that dropped a column cannot be undone by rolling the code back alone; that is what the pre-upgrade backup is for.
HiLMS is MIT-licensed. No replicants were harmed in the writing of these books.