Upgrade
Move an installation to a new image with one launcher command, check it and go back if needed.
1. Before you start
-
Verify and build the new kit as in installation. Note the image name it prints:
NEW_IMAGEbelow. -
Let current calculations finish and pause new changes.
-
Make sure the host Python has the packages from
backend/requirements.txt: the launcher takes a backup before changing anything. -
If a tool on the server deletes unused Docker images, as Coolify's Docker cleanup does, save the current image first, for example
docker save -o PRIVATE_PATH/backups/CURRENT_IMAGE.tar CURRENT_IMAGE: after the upgrade it is unused, and you need it to go back.docker load -irestores it. -
Read the schema changes the new version brings: some need an action in the application after the upgrade.
Never delete volumes or repeat first-time setup to upgrade. Changing --image alone does not upgrade an existing installation: the image is recorded in its private directory.
2. Upgrade a launcher installation
From the kit of the currently installed version (the backup it takes checks the schema against that kit):
python3 scripts/start_server.py --directory PRIVATE_PATH --upgrade-image NEW_IMAGEA local installation uses python scripts/start_local.py --directory PRIVATE_PATH --upgrade-image NEW_IMAGE.
The launcher then:
- checks that the new image exists and differs from the current one;
- writes a backup to
PRIVATE_PATH/backups/before-upgrade-<UTC time>.dump; if the backup fails, nothing is changed; - stops the API and worker;
- records the new image, and the previous one, in
image.jsonandworkspace.json; - starts the installation: setup runs the database migrations before the API and worker start.
On a 2 vCPU / 8 GB VPS shared with Coolify, this took 19 s including the backup; see Install on a VPS, step by step. Use the new version's kit for later maintenance. Later starts of a server installation pass the new image with --image; the launcher refuses values that differ from the recorded installation.
3. If the upgrade does not start
The launcher reports: “Upgrade did not start. If setup failed, API and worker stay stopped and the database is preserved.” It also names the previous image.
Keep that database for diagnosis. Do not reset the volume and do not start the previous image on the migrated database: an older image may not support a newer schema.
4. Go back to the previous version
There is no one-step backout command. With the previous version's kit:
- Restore the
before-upgradebackup into an isolated database, as in Backup and restore. - Start the recorded previous image (
previous_imageinworkspace.json) against that copy. - Validate sign-in, data and a calculation before reopening access.
5. Upgrade a Compose installation without the launcher
For an installation configured in .env, with any overlays it uses repeated on every Compose command that changes services:
python scripts/database_backup.py backup
docker compose --env-file .env -f deploy/docker-compose.example.yml stop api worker
docker compose --env-file .env -f deploy/docker-compose.example.yml up --build -dTake the backup with the kit of the installed version. If setup fails, keep the API and worker stopped and correct the reported configuration or schema issue before retrying.
An installation from an earlier version that used the bootstrap database account needs a trusted PostgreSQL backup made with that account first, then the separate runtime and migration credentials of the configuration reference.
6. How migrations run
The setup service runs python -m app.setup_database with the same image as the API. It prepares the managed database roles, runs the migrations as the migration owner and grants the runtime role its permissions. The API waits for setup to succeed; its normal startup never runs migrations or inserts data.
Migrations keep existing identifiers and edits, but some transform data, such as 0012_weekly_hours. Their downgrades are not a backout procedure: go back by restoring a backup, as in step 4.
7. Check the upgraded installation
docker compose psshows the API, worker and database healthy;GET /api/v1/readyanswers.- A named user signs in and keeps their account language.
- Existing depot, vehicles, deliveries and saved plans are present.
- Planning shows no Calculation service needs attention banner.
- A representative calculation produces a reviewable result.
- The actions the new migrations need, described below, are done.
Reopen access only after these checks.
8. Migrations 0009 to 0017
An earlier installation receives all of these migrations during the upgrade: setup applies them in order, with no extra command. This version expects the schema revision 0017_safety_margin; GET /api/v1/ready and the backup command check it.
| Migration | Effect |
|---|---|
0009_address_lookup | Adds the company's choice to use IGN's public address service, off for every company. The runtime role may change this column. |
0010_order_numbers | Adds the delivery number format, absent until an administrator chooses one, and the starting number (1 by default). The runtime role may change these two columns. |
0011_depot_address | Adds the depot's postal address, empty for an existing depot. |
0012_weekly_hours | Replaces each vehicle's dated availability window with weekly working hours and dated exceptions. A company vehicle's former window becomes an exception on its date, with no weekly hours invented; the vehicles of a sample workspace work their hours every day. |
0013_customers | Adds the saved customers table. |
0014_delete_orders | Adds the function public.delete_orders, through which the runtime role deletes a company's chosen deliveries. |
0015_depots | Allows several depots and places per company: each existing depot becomes its company's main depot. Adds the function public.delete_depot. |
0016_examples | Adds the example mark to depots, vehicles and deliveries, and the function public.remove_examples, which replaces public.start_company_from_sample. A sample workspace created by first-use setup becomes a company workspace whose vehicles and deliveries are examples; its depot stays the main depot. The legacy local demonstration without accounts (APP_MODE=local_demo) stays a sample. |
0017_safety_margin | Adds the company's safety margin in minutes, 15 for every company, limited to 0 to 120. The runtime role may change this column. |
The three functions belong to the migration role; setup grants the runtime role only the right to execute them.
After the upgrade:
- Vehicle hours. After
0012_weekly_hours, a company vehicle works only on the date of its former window: a calculation for any other day refuses it (VEHICLE_OFF_DAY). Set each vehicle's week once in Fleet, or re-import the vehicle file with the new columns. - Former sample workspace. After
0016_examples, the company keeps the fictional Paris depot as its main depot. Before planning real deliveries, replace its address and entrance with your depot's, add your vehicles, then remove the examples in Settings → Example data. - Integrations. The vehicle CSV replaces
available_start,available_endwith one column per weekday (monday…sunday, for example07:30-17:00, empty for a day off).POST /api/v1/vehicles/availabilityandPOST /api/v1/workspace/companyno longer exist;POST /api/v1/setuprefusesworkspace_kindand requiresdepot. See the API reference. - Safety margin. After
0017_safety_margin, new calculations plan every delivery to start at least 15 minutes before its window ends, where they used to plan up to its last minute. A delivery that can only be reached in those last minutes may now stay unassigned. An administrator changes the margin in Settings → Calculation (0 plans up to the window's end). Plans calculated before the upgrade show their margin as None: planned up to the last minute.
The downgrades of 0015_depots and 0016_examples refuse to run while other depots or places, or examples, exist; the downgrade of 0012_weekly_hours restores only one window per vehicle. To go back to the previous version, restore the before-upgrade backup, as in step 4.