Migration Ordering at Deploy Time
Migrations run as a distinct step of every deploy, before the API starts, and their ordering is strict and deterministic. Getting this model right is what lets core and any number of plugins share one database safely.
The order
- All core migrations run first.
- Then plugin migrations, discovered and applied by the shared script
scripts/apply-plugin-migrations.sh, which scansplugins/*/migrations/for SQL files. - Across plugins, migrations run in alphabetical entity-slug order (the script's
sort); within a plugin, in filename order.
This runs the same way in CI (fresh schema + plugin migrations → test DB) and in deploy (incremental migrations against the live DB). Plugin authors never touch CI/CD YAML — the script finds their migrations by directory.
Forward-only
Migrations are forward-only. Rolling an image back to a previous version does not roll back the schema (see Rollback & Environment Recovery). Design migrations to be idempotent (CREATE TABLE IF NOT EXISTS, guarded ALTERs) so re-application on every deploy is safe.
A day-0 caveat worth knowing
Some migrations are written as day-0 migrations — they assume a table is empty when first applied. The workflow-engine registry migration (011), for example, adds a NOT NULL workflow_engine_id column to workflow without a default, which only works cleanly if workflow is empty at that point; it's idempotent on later runs. If you're introducing a migration like this into an environment that already has data, that's exactly the kind of thing to plan for before deploying.
"No plugins" is valid
A deployment with zero plugins is a perfectly valid state — the plugin-migration step simply finds nothing to apply. Plugins are additive; the platform stands up with or without them.