Upgrades
CairnCMS follows semantic versioning. Most upgrades are routine: pull the new image, restart the container, and the database catches up automatically on startup. The work that does need attention is the small amount of structural and operational care around an upgrade, including taking a backup, reading the changelog, choosing the right window, and rebuilding extensions when a major version moves the host range.
This page covers the upgrade procedure, the rollback path, and the considerations specific to multi-instance and major-version upgrades.
Versioning policy
Section titled “Versioning policy”CairnCMS uses semver. For a given version MAJOR.MINOR.PATCH:
- Patch (
1.2.3→1.2.4) — bug fixes and security patches. No schema changes that require a code change on your side, no breaking API or extension contract changes. - Minor (
1.2.3→1.3.0) — new features, additive changes. The HTTP API, SDK, and extension contracts stay backwards-compatible. Database schema changes happen here, but always through migrations that run automatically. - Major (
1.x→2.x) — breaking changes. Possible API contract changes, extension SDK changes, and migrations that require operator attention. Major upgrades are documented in dedicated migration notes alongside the release.
The version is stamped into every container image tag (cairncms/cairncms:1.2.3) and reported by cairncms --version on the CLI. The running platform reports its version through the /server/info API endpoint.
Before you upgrade
Section titled “Before you upgrade”Three things every time, no exceptions:
- Take a backup. A full database dump and, if files have changed since the last backup, a copy of the storage volume. See Backups.
- Read the changelog for every version between yours and the target. Even within a single minor range, you might have skipped a release that introduced a configuration default change or a deprecated environment variable.
- Test in a non-production environment first if you can. A staging instance restored from production data, run through the upgrade, is the cheapest way to surface upgrade-time problems before they reach users.
For major-version upgrades, also:
- Audit your extensions. Their
cairncms:extension.hostsemver range inpackage.jsondeclares which CairnCMS versions they are compatible with. An extension built for^1.0.0will not load against2.xuntil it is rebuilt with a compatible range. - Review breaking-change notes for any deprecated environment variables, removed flags, or schema changes that need manual intervention.
The standard upgrade
Section titled “The standard upgrade”The procedure is the same for any patch or minor version, and most major versions:
Docker image
Section titled “Docker image”# Pull the new tagdocker pull cairncms/cairncms:1.3.0
# Update your compose file or deployment manifest to reference 1.3.0# Stop the running container, start the new onedocker compose up -d cairncmsThe image’s default CMD runs cairncms bootstrap on startup. Bootstrap ensures system tables exist and applies pending migrations. After migrations complete, cairncms start boots the API.
For a multi-host or orchestrated deployment, the equivalent step is whatever your platform does to rotate the running version: a kubectl rollout restart, an ECS service update, a Fly deploy, and so on.
Host install
Section titled “Host install”If you run CairnCMS directly on a host:
npm install -g cairncms@1.3.0cairncms bootstrap# Restart your process supervisor (systemd, PM2, etc.)cairncms bootstrap is idempotent meaning it’s safe to run on every upgrade. It applies pending migrations and flushes the schema cache.
What bootstrap does on upgrade
Section titled “What bootstrap does on upgrade”On a deploy that bumps the CairnCMS version:
- Bootstrap reads the database’s
directus_migrationstable to determine which migrations have already run. - It applies any newer migrations the upgraded version ships, in version order, and records each one as it completes.
- After all pending migrations are applied, it flushes schema and permission caches so the next request sees the new structure.
cairncms startboots the API. On startup,startitself only validates that all known migrations have been applied; it does not run migrations.
For most deployments, this means “pull the new image, restart” is the entire upgrade. The platform handles the rest.
Multi-instance upgrades
Section titled “Multi-instance upgrades”Behind a load balancer with multiple CairnCMS instances, the migration step needs to run exactly once, not once per instance. The migrations runner does not coordinate across instances. It reads directus_migrations, applies whatever has not been applied yet, and inserts the completion rows. Two instances running bootstrap at the same time will both read the same pending list and both try to apply the same migrations, which races at best and corrupts state at worst.
The remedy is to keep the migration step out of the steady-state pod startup:
- Run
cairncms bootstraponce against the database, typically as a one-shot job or init container, and wait for it to complete. - Once migrations are done, roll the fleet onto the new image. The new instances start, observe that all migrations are already applied, and serve traffic.
In Kubernetes, the cleanest shape is a Job (or init container) that runs cairncms bootstrap before the rolling update of the main Deployment. The Job blocks the rollout if migrations fail, which surfaces the problem before any user traffic hits the new version.
For a single-host Docker Compose deployment that scales to one CairnCMS replica, the default image CMD (which runs bootstrap then start) is fine. There is no second runner to race against. The split job pattern only matters once you have more than one instance.
Rolling back
Section titled “Rolling back”Rollback is the reverse of the upgrade with one extra step.
Patch and minor version rollback
Section titled “Patch and minor version rollback”If the database has not been changed (no new migrations applied), rollback is just an image rollback:
docker pull cairncms/cairncms:1.2.3docker compose up -d cairncmsIf migrations did run, roll those back first:
# One step at a time — each invocation reverses one migrationcairncms database migrate:downcairncms database migrate:down# ...until you are at the target version's expected migration statemigrate:down is destructive. The migration’s down() is responsible for reversing the schema change, but data added after the migration ran can still be lost (a column that was added by the migration and populated since cannot be restored after the column is dropped). When in doubt, restore from the backup taken before the upgrade rather than relying on down migrations.
Major version rollback
Section titled “Major version rollback”A major version is, by policy, allowed to ship migrations that do not have a clean down path. Treat major-version rollback as a restore-from-backup operation, not a step-down operation. The pre-upgrade backup is the source of truth; reapply it to a 1.x instance on the previous image tag, redirect traffic, and investigate the upgrade failure offline.
This is also why the pre-upgrade backup is non-negotiable. Without it, a major-version rollback may not be possible.
Custom migrations and upgrades
Section titled “Custom migrations and upgrades”Custom migrations placed in EXTENSIONS_PATH/migrations are interleaved with platform migrations by version timestamp at runtime. This has two consequences for upgrades:
- Newer custom migrations run during the upgrade. If you have added a custom migration since the last deploy, it runs as part of
bootstrapalongside any new platform migrations. - A custom migration with a timestamp earlier than the last applied platform migration will not run. The migrations runner only applies versions newer than the latest completed one. Pick custom-migration timestamps that put them after any platform migrations they depend on.
See Custom migrations for the file format and CLI commands.
Extension compatibility
Section titled “Extension compatibility”Each extension’s package.json declares a host range:
{ "cairncms:extension": { "host": "^1.0.0" }}This field is informational. CairnCMS surfaces it in extension metadata so operators and tooling know which platform versions an extension was built against, but the loader does not enforce the range. An extension whose declared host excludes the running platform version still loads.
In practice, that means extension compatibility is your responsibility to manage during upgrades. For minor and patch upgrades within an extension’s declared range, the extension keeps working without intervention because the SDK contract is stable. For major upgrades, where the SDK contract may change:
- Update each extension’s
hostrange to include the new major. - Rebuild against the new SDK version with
npm run build(which callscairncms-extension build). - Run the extension’s tests, if any, against the upgraded SDK.
- Reinstall or redeploy the extension alongside the platform upgrade.
If you skip the rebuild on a major upgrade, the extension may still load, but it can fail at runtime when it calls SDK helpers whose contracts have changed. Treating the rebuild as part of the major-upgrade procedure is the safe default.
Bundles use the same host field; rebuilding a bundle rebuilds all of its entries.
Where to go next
Section titled “Where to go next”- Backups — the prerequisite for any upgrade you would actually be willing to roll back from.
- Deployment — covers the bootstrap-and-start pattern in more detail, including the Kubernetes init-container shape.
- Custom migrations — the file format, version naming, and CLI commands for migrations you write yourself.