Migrating to calServer 6.0¶
6.0 is a version and consolidation cut: the version number catches up with the name the documentation has been using all along, and the version spaces across all layers converge on a single source. Nothing is removed functionally. The legacy teardown is marked and scheduled — see Deprecations.
Background and rationale: ADR-012.
First: back up¶
Required, not a formality. A rollback via /admin/updates restores the
containers, not the database schema. Crossing a major boundary without a
backup leaves you with no way back for the data.
# PostgreSQL (V2 default)
docker exec calserver-postgres \
pg_dump -U calserver calserver_v2 > backup-before-6.0.sql
# MySQL (V1 and transitional installations)
export MYSQL_PWD="$(sed -n 's/^DB_ROOT_PASSWORD=//p' /var/calserver/.env | head -1)"
docker exec -e MYSQL_PWD mysql_db \
mysqldump --single-transaction --routines --triggers \
-u root calserver > backup-v1-before-6.0.sql
unset MYSQL_PWD
Database names and users come from the host .env: PG_DATABASE /
PG_USERNAME for V2 (without an entry, calserver_v2 and calserver apply),
DB_DATABASE for V1. The MySQL container is named mysql_db.
Do not fall back to -p for the mysqldump
Without -i, docker exec attaches no stdin. The password prompt of -p
then receives nothing, mysqldump connects with an empty password and
reports Access denied ... (using password: NO). -it is the wrong way
out: the pseudo-terminal puts CRLF line endings into the redirection, and
the dump looks right but is broken on restore.
Hence -e MYSQL_PWD without =value — Docker takes the value from the
calling environment, so the password never appears in the host's process
list. --single-transaction gives a consistent snapshot without locking
the running V1 (all tables are InnoDB); it is the same flag set V2's own
backup run uses (app/Services/Backup/DatabaseDumper.php).
Then check the last line:
If the disk fills up during the dump, a plausibly sized file is still there, just without an end. And be able to restore the backup, not just take it: replay it once in a test environment before it matters.
Order¶
- V2 first, via
/admin/updates. The updater recreatescalserver-api-v2andcalserver-frontend; migrations run automatically on container start. - V1 afterwards, via
deploy.shon the host. The updater deliberately leaves the V1 containers alone, so after step 1 there is a version offset. The admin UI shows the running V1 tag.
The major gate¶
From 6.0 on, crossing a major boundary no longer runs as an ordinary update.
On /admin/updates the button reads "Prepare major update" instead of
"Update", followed by two steps:
- Read the migration notes and the releases between the running and the target version, along with the notes on the V1 offset and the rollback limit.
- Confirm the backup and type the target version. Only then can the update start.
Technically both sides require the confirmation independently: the updater
sidecar answers 409 without confirm_major, the V2 API answers 422.
Fleet-wide switch¶
ALLOW_MAJOR_UPDATE in the host .env |
Effect |
|---|---|
| unset (default) | major update requires the confirmation in the UI |
true |
major update runs without a prompt (unattended fleets) |
false |
major updates are blocked, even when confirmed |
false is the emergency stop for managed fleets: customers see 6.0 but cannot
apply it until the operator releases it.
Beta channel¶
Releases now go through the beta channel first. To test 6.0 before the
stable release, set this in the host .env:
The beta channel accepts tags with a -beta or -rc suffix in addition to
stable ones. A beta system should run a real semver tag as V2_IMAGE_TAG
(v5.64.922), not develop-latest — otherwise the running version cannot be
determined and the UI explicitly marks the comparison as imprecise.
Update ALLOWED_TAG_PATTERNS on existing systems
If you set ALLOWED_TAG_PATTERNS yourself in the host .env, extend the
pattern with the pre-release suffix, or the check finds a beta tag and
applying it fails with 403:
ALLOWED_TAG_PATTERNS=^v\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$|^(develop|release|master|beta)-latest$|^latest$
Without a custom entry the new default applies and there is nothing to do.
Behavioural changes¶
Small, but visible to API clients:
| What | Before | From 6.0 |
|---|---|---|
meta.api_version on 17 endpoints |
"2.0" |
"2.0.0" (from config/api.php, as everywhere else already) |
GET /admin/information/version → app_version |
"2.0.0" (from composer.json) |
product version, e.g. "6.0.0" |
GET /app/version → app_version |
product version | unchanged |
dcc:release in the DCC XML export |
always "1.0.0" |
product version |
Version log update_logs |
— | new field is_major |
The API contract is unchanged. The route prefix /api/v2 does not move
with the product major, and meta.api_version only rises when the contract
itself changes. Existing integrations need no adjustment.
What 6.0 does not do¶
- Nothing is removed.
V1CompatResponse, the X-REST header auth, the MD5 password path andHasFieldMappingare marked deprecated and scheduled for 7.0 — see Deprecations. - V1 (Yii) is not switched off. The sunset ahead of the Yii 1 EOL on 2026-12-31 is a separate step.
- There is no database switch as part of the update.
If something goes wrong¶
- Rollback via
/admin/updates: every completed or failed history entry offers the way back to the previously running tag. The way back across the major boundary is deliberately not blocked. - Restore the schema: the rollback brings the containers back, not the schema. That is what the backup is for.
-
If the update hangs in "applying", check on the host:
Details on the update system: Update guide.