Versioning and breaking changes
An update should add capability without asking you to migrate your data by hand or reinstall anything. That is a design rule with teeth, and here is how it is kept.
Migrations are additive first
Every schema change ships as a numbered SQL migration that runs on the api's first boot after the update. Each migration runs inside its own transaction, so it either applies fully or not at all: a partial change is never left behind.
The rule the migrations follow is add, don't rewrite. A new feature adds a column or a table; it does not drop or rename one out from under the running app. When a change does have to remove something, it is split across releases: an earlier release adds the new shape and writes to both old and new, a later one switches reads over, and only a release after that drops the old column. Because of this, an older image keeps working against a newer schema (the extra column it does not know about is simply ignored), which is what makes stepping back to a previous version safe. See Rollback.
Renaming a field never moves your data
Fields are stored by a stable internal name, and the label you see is a presentation layer resolved when a form loads. Renaming a field changes the label, not the storage key, so the values stay exactly where they were. There is no data migration on a rename, ever. The same holds for hiding, reordering, or recoloring a field.
Self-heal, not reinstall
When a shape does change, the fix runs itself. On boot the api reconciles each workspace against the current code: a module that added a migration after a workspace enabled it gets caught up automatically, and a workspace that predates a newer structure is brought forward in place. You are never asked to uninstall and reinstall a module or a workspace to pick up a change. If an update ever did require that, it would be a bug, not a procedure.
What "breaking" means in practice
For a self-hoster, the change that hurts is one that requires manual work or
loses data on the way up. The discipline above is aimed squarely at removing
both. Pin COBBLR_VERSION if you want to read
each release's notes before moving, rather than always taking the latest. When a
release does need a one-time action, it is called out; the default assumption is
that docker compose pull && up -d is all an update takes.