Upgrades
Upgrading Miabi is intentionally simple: pull a newer image and recreate the containers. Miabi handles the rest on startup.
Automatic migrations
On every startup, Miabi brings the database in line with the running binary:
- Schema migrations are applied automatically (GORM
AutoMigrate). - Ordered data
upgradesteps run in sequence, with each applied step recorded in theupgrade_stepstable so it never runs twice.
Because both happen automatically, upgrading is just a matter of starting the new version. You watch the logs to confirm migrations completed before the instance serves traffic.
Update notifications
Once a day, Miabi asks GitHub whether a newer release exists and shows platform admins a dismissible
notice with a link to the release notes. The check only notifies — nothing upgrades on its own.
An upgrade is always something you ask for, whether by re-running the installer (Compose) or by
running miabi upgrade (stack).
The check is channel-aware: a pre-release build is offered newer pre-releases and stable releases; a stable build is never nudged onto a pre-release. Dismissing a notice hides it until the next version appears.
| Variable | Default | Purpose |
|---|---|---|
MIABI_UPDATE_CHECK | true | Set false to disable the check entirely (air-gapped hosts, or to avoid the outbound call) |
Nothing about your install is sent: it is an unauthenticated GET to api.github.com identified
only by User-Agent: miabi/<version>. No install id, no telemetry. A dev build never checks.
Admins can read the cached result at GET /api/v1/admin/update.
Runners and agents
Build runners and node agents ship and version independently of the panel — upgrading Miabi does not upgrade them, and a fleet quietly drifts years behind if nobody looks. The same daily check therefore also reads the newest release of each, and compares it against the version every instance reports when it connects.
Where it shows up:
- A banner for platform admins: "3 of 5 runners are behind v0.0.9."
- An outdated badge on the runner's page and on the node's agent version, linking to that project's release notes.
GET /api/v1/admin/update/components— the newest release of each, and how many instances are behind it.
Only the stable line is used as the yardstick. A component has no single running version to infer a channel from — a workspace may run ten runners on ten versions — so a release candidate never marks a fleet outdated, and an instance running one is not flagged for being ahead.
Only instances that are up are considered. An offline runner or an unreachable node is not something you can upgrade right now — it may be decommissioned, or a laptop that is simply shut — so it is neither badged nor counted. A runner that is draining still counts: its tunnel is live, it is just finishing its jobs.
An instance that has never reported a version is not counted either way. Silence means unknown, not current; a badge that guesses is a badge people learn to ignore.
Upgrading is manual and per component, as it is for Miabi itself: pull the newer runner image and
restart it, or re-run the agent install script on the node. MIABI_UPDATE_CHECK=false disables all
of it together.
Version notes
Goma Gateway 0.14 — forwarded headers are only believed from a trusted proxy
Miabi now provisions Goma Gateway 0.14, which brings the oidc middleware and one change that
can break a working install.
The gateway used to trust X-Forwarded-Proto and X-Forwarded-For from anyone. It now believes them
only when the request arrives from an address listed in its proxy.trustedProxies. That is the
correct behaviour — without it any client can forge those headers and spoof its own IP — but it
changes what happens when something terminates TLS in front of the gateway.
Who is affected: installs where Cloudflare, nginx, HAProxy or a cloud load balancer terminates
TLS and forwards plaintext to the gateway, and whose goma.yml has no proxy: block.
What breaks: the gateway sees a plaintext hop, decides the request is not HTTPS, and the
redirectScheme middleware redirects it — back to the terminator, which forwards plaintext again.
The request loops until the client gives up. Miabi puts that middleware on the built-in container
registry route, so docker push and docker pull are usually the first thing to fail.
The fix is to tell the gateway what is in front of it, in goma.yml:
proxy:
enabled: true
trustedProxies:
- "10.0.0.0/8" # your terminator's address or range
# For Cloudflare, use its published ranges: https://www.cloudflare.com/ips/
ipHeaders:
- "CF-Connecting-IP" # keep first when behind Cloudflare
- "X-Forwarded-For"
trustedProxies must not be empty — an empty list with enabled: true is rejected at load, since
nothing would separate a proxy from a client that simply sends the header itself.
If you cannot configure that, MIABI_REGISTRY_HTTPS_REDIRECT=false drops the redirect from the
registry route as an escape hatch. It is the lesser fix: it stops the loop without giving the gateway
the real client IP, which request logging, rate limiting and IP allowlists all depend on.
An install where the gateway itself terminates TLS — the default Compose and miabi setup
topologies — needs no change. The connection really is HTTPS, so no forwarded header is consulted.
Back up first
Always back up your database before upgrading, and especially before a major-version upgrade. Migrations modify your schema and data in place. A backup is your only way back if something goes wrong — see Backups.
Upgrade procedure
How you upgrade depends on who owns the containers. Miabi labels every one of them, so it always knows — and refuses to act on a stack it does not own.
- Installer /
docker run(whatget.miabi.iobuilds) →sudo miabi upgrade, ordocker run … miabi/miabi:<tag> upgradeif you installed without the CLI. Miabi replaces its own container, rolling back if the new one does not come up. (updatestill works, deprecated.) - Compose, if you set it up yourself →
docker compose pull && docker compose up -d.
If you are not sure which you have, ask:
docker inspect miabi --format '{{index .Config.Labels "io.miabi.managed-by"}}'
# miabi → installed by `miabi setup`
# compose → Compose install
Upgrading
sudo miabi upgrade
miabi upgrade rolls the stack to the latest published Miabi release, looked up when the command
runs. The version is not baked into the CLI: the CLI releases on its own cadence, so a build-time pin
would freeze every install at whatever was current when that CLI was built — and an older CLI could
never install today's Miabi. To choose a version yourself, or to upgrade with no network:
sudo miabi upgrade --version 1.8.0 # or v1.8.0 — the leading v is optional
sudo miabi upgrade miabi-gateway --version 0.14.0
sudo miabi upgrade --image registry.example.com/miabi:1.8.0
--version swaps only the tag on the component's current reference, so a private registry and
non-Miabi components (the gateway) keep working. --image replaces the reference outright; the two
cannot be combined.
Miabi pulls the image, replaces the running control plane and waits for it to come back healthy.
latest, edge, main and friends are refused a clean rollback. The rollout skips its automatic
rollback when the previous reference equals the new one — so a failed :latest upgrade has no
distinct previous image to return to. Drift detection compares references too, so an old :latest
and a new one look identical and the next upgrade reports "already at" without doing anything.
Miabi warns when you use one.
It can replace its own container because the thing driving the upgrade is not that container —
it is the miabi binary on the host. That is the whole reason Miabi owns its containers rather than
letting Compose own them.
If the new version does not come up
The rollout is not a blind cutover:
- For components that can safely run a second copy (the gateway), the new image is started under a throwaway name first, watched, and only promoted once it is healthy — a gateway that boots but serves nothing never reaches the live one.
- The previous image is remembered before anything is replaced.
- If the new container never becomes healthy, the previous image is restored automatically and
the manifest is reverted, so
miabi.yamlnever claims a version that is not running.
verifying
rolling-back miabi did not become healthy within 1m30s
rolled-back … rolled back to miabi/miabi:1.3.0, which is running
Restoring the previous image does not undo a schema migration the new version already applied. Miabi's migrations are additive, so an older binary against a newer schema generally works — but the supported recovery path for a genuinely bad upgrade is still to restore the pre-upgrade backup. See Downgrades below.
Restarting without upgrading
A restart re-reads what is on disk — most usefully the gateway's goma.yml, which Goma does not
hot-reload (it watches its providers directory, not its base config):
sudo miabi stack restart miabi-gateway # or `miabi stack restart` for the whole stack
The config is validated before anything is stopped, so a broken edit cannot take the gateway down.
A restart cannot apply a manifest change — that needs miabi setup, which recreates — and it
says so rather than leaving the edit looking ignored.
Changing anything else
Everything else — the gateway version, the registry, TZ, the log level — lives in
/etc/miabi/miabi.yaml. Edit it and re-run:
sudo miabi setup
The converge is idempotent: components whose configuration did not change are left alone, and only what actually changed is recreated. Bumping PostgreSQL is therefore something you ask for by name, not a side effect of upgrading the panel.
Upgrading a Compose install (if you set one up yourself)
The supported path is to re-run the installer: it stamps the release's exact image tags into
.env and brings the stack up.
# 1. Back up first (see /docs/storage/backups)
# 2. Re-run the installer — it rewrites MIABI_IMAGE / GOMA_IMAGE / RUNNER_IMAGE
curl -fsSL https://get.miabi.io | sudo bash
# ...or pin an exact release
curl -fsSL https://get.miabi.io \
| sudo MIABI_VERSION=v1.4.0 bash
# 3. Watch the logs for the migration confirmation
cd /opt/miabi && docker compose logs -f miabi
To upgrade by hand instead, edit .env and set the image, then recreate:
# .env
MIABI_IMAGE=miabi/miabi:1.4.0
docker compose pull && docker compose up -d
MIABI_VERSION is an installer variable (a git tag, e.g. v1.4.0) — the server and
compose.yaml never read it. Setting MIABI_VERSION in .env does nothing. The variable compose
reads is MIABI_IMAGE (an image reference, e.g. miabi/miabi:1.4.0, with no leading v).
Wait for a line similar to database migrations applied in the logs. Once it appears, the schema and data steps are complete and the instance is running the new version.
Downgrades are not supported
Miabi rolls forward only. The upgrade_steps table tracks which steps have been applied, and there are no reverse steps — once a migration has run, the previous binary may no longer understand the schema.
MIABI_ALLOW_DOWNGRADE=true exists as an escape hatch, but it does not undo migrations. Only set it if you fully understand the schema implications of running an older binary against an already-migrated database. The supported recovery path for a bad upgrade is to restore the pre-upgrade backup.
Where to go next
- Backups — take a backup before every upgrade.
- Configuration — pinning the image version and other
.envsettings. - Platform Settings — instance-wide configuration.