Skip to content

Update system

The update system is the one part where a site can destroy itself; that is why it was designed by comparing the approaches of other systems.

Comparison

How is the update done? Notable
WordPress One click from the panel; core, plugins and themes separate Has partial packages (small download); signature verification is limited to the core's own package
Drupal 11 Code and schema are separate steps; the change is made on a copy and passes checks Has signature support but it is skipped by default; no automatic database backup
Sulu 3 No panel updater; composer update and CI The web process cannot write code, smallest attack surface; Composer, CI and SSH are mandatory
CPalius One click from the panel Signed manifest, maintenance mode, atomic writes, DB backup, automatic rollback

The CPalius flow

  1. Release information is verified: the Ed25519 signature of latest.json, expiry (30 days) and downgrade check. If it fails, nothing is downloaded.
  2. Lock and maintenance mode: two updates do not collide; visitors see a short 503 page.
  3. A database snapshot (db-<version>.sql.gz) is taken.
  4. Files are written in two steps: first each is prepared next to its target (.cpnew), then all of them take their place with rename in a short loop. If something goes wrong during preparation, the live files have not been touched.
  5. If something goes wrong: files and database are restored together. If the process dies halfway, the next request restores the files in public/index.php, before the framework is loaded; the database restore finishes on the first request that opens.

Limits

  • Rollback deletes everything written to the database between the snapshot and the failure.
  • A short 503 page is shown while the update runs.
  • The signature has a 30-day lifetime; if no new release comes out, it is refreshed by hand.

Channels and signature renewal: Channels and signing.

After the files: cp:update

When the new release's files take their place, adapting the rest of the site to the new code is the job of UpdateRunner (the cp:update command or the panel). The order is the job itself:

  1. Migrations: schema first; everything after can rely on a new column.
  2. Update hooks: data fixes implied by the schema.
  3. Module upgrades: the upgrade() operation of every module whose version advanced, and its newly numbered SQL migration files. The core is now up to date.
  4. Configuration import: declarative state comes after the code that understands it.
  5. Cache rebuild: the compiled container is deleted and the AssetMapper digests are rewritten. (The package is a Git snapshot; public/assets does not go into Git, and without this step new import entries return 404.)

Re-runnability

Every stage records its own progress (migration table, hook ledger, module version setting). A run interrupted halfway can be run again: finished work is skipped, not repeated. That is why the updater collects errors where it can safely continue and does not abandon the operation; it avoids leaving things in an in-between state nobody can name.

Writing an update hook

A migration changes the schema; a hook changes data or does anything DDL cannot express: rebuilding the flat index when a field type changes, rewriting a setting value, back-filling a newly added column.

final class BackfillSummaryHook implements UpdateHookInterface
{
    public function id(): string { return 'portal.backfill_summary'; }   // never changes
    public function version(): string { return '2.2.51'; }                 // which release it belongs to
    public function description(): string { return 'Fill summary from body'; }
    public function run(): ?string { /* idempotent work */ return null; }
}

The contract:

  • The id never changes. The "done" record is the id; renaming runs the hook a second time.
  • It is tied to a version. Even if several releases are skipped, hooks are applied in version order.
  • It must still be idempotent. The ledger is written after the hook returns; if it dies in the middle of the hook, it runs again on the next attempt.

Update notifications

Administrators see core, module and theme updates without waiting: the core.release_check cron job refreshes the core release information, the patch notice and the signed extension catalogue every 6 hours, then, if there is a new release, leaves a "A new version of {name} has been released" note in every administrator's notification box (the bell in AACP and Studio). Clicking the notification opens the relevant screen (Updates for the core, the store for modules and themes).

A notification for the same release drops once (deduplicated by an item + version key); if a newer release of the same item comes out, a new notification arrives. An extension release that is incompatible with the core is not notified. If cron is not running, the store and Updates screens refresh on visit as before; so the notification is a bonus for sites where cron runs regularly.

For the operator

Job How
Check if there is an update AACP → System → Updates
Apply from the command line php cp-core/bin/console cp:update
See beforehand what will happen cp:update --dry-run (--json for machine-readable output)
If something goes wrong The previous files are under cp-core/var/update/backup-<version>/
Last updated: 13.10.2026