Two different things are called "an update", and they behave very differently:
| Update | What moves | Downtime | Automated rollback |
|---|---|---|---|
| DataMind OS update | The services the Installer deploys | Per service, while containers are recreated | No |
| Installer self-update | The Installer's own backend container | The Installer is unreachable while it restarts | Yes — one attempt |
Only the Installer performs it, but the Installer performs it on request — including a request that comes from DataMind OS. A DataMind OS update is always an authenticated call to the Installer:
POST /api/deployment/deploy POST /api/deployment/compose-up POST /api/deployment/apply-config POST /api/deployment/pull
The Installer's interface calls these. DataMind OS components authenticate to the Installer with a shared service token, so the platform can drive deployments the same way — the Installer's job is to do the work, not to decide who may ask.
Whether a DataMind OS screen exposes an "update" control is a DataMind OS question, not an Installer question. From the Installer's side the endpoint is the endpoint, whoever calls it.
Yes. The title bar carries a version pill: when a newer build is published on your channel it becomes an actionable "update available" indicator, and confirming it starts the self-update.
Under the hood that is POST /api/system/self-update, which is a real API call you can also make
directly:
curl -X POST http://localhost:8000/api/system/self-update \ -H "Authorization: Bearer <access-token>"
Technically yes, through the shared service token; the Installer does not restrict that endpoint
to a role. POST /api/system/self-update requires authentication but declares no role
requirement, and the authentication guard accepts a call carrying the shared service token as an
administrator-level caller. Anything that holds that token can therefore start a self-update.
That is a deliberate trust boundary between two first-party services, not a user permission. If you are assessing risk, treat the service token as the thing that grants this, and guard the place it is stored accordingly. See Access and security.
Expect the Installer's interface and API to be unavailable while the backend container is replaced — typically a minute or two, with a hard ceiling of five minutes in the switch step. The work is sequenced to keep the visible outage as short as possible:
--wait-timeout 300.DataMind OS is not touched by a self-update. The confirmation dialog says so explicitly, which is the point: you can move the Installer forward without disturbing the platform it manages.
The interface reconnects by polling the Installer's health endpoint every 3 seconds, and gives up after 10 minutes with an instruction to reload or check the host. A wait that long means the switch did not come back — see the rollback question below.
Only the services that actually change are recreated, but each recreation is a short outage for that service. The Installer's own confirmation states that running services restart briefly, and that an update touching critical services should be expected to cause a brief outage.
For a targeted rollout, Apply changes is stricter and safer than Update: it recreates services in dependency tiers and waits for each tier to become healthy before starting the next one, and it fails the job if a recreated service crashes immediately.
Omit the body entirely to target everything; name services to target them only:
curl -X POST http://localhost:8000/api/deployment/deploy \
-H "Authorization: Bearer <access-token>" \
-H "Content-Type: application/json" \
-d '{"services":["lurien"]}'Only one job may run at a time. Starting a second deployment job while one is running is refused with a conflict, and a self-update refuses to start while any job is running — it would kill the job mid-flight.
It rolls back by itself, once. The helper script's escalation chain is deliberately short: one
switch, one rollback, then stop. If the new image fails to come up healthy within the timeout, the
helper restores the compose file it backed up and brings the service up again pinned to the
rollback tag — the image that was running before the update, re-tagged during pre-flight.
The outcome is recorded and reported:
| Outcome | What you see |
|---|---|
ok | The new build is running |
rolled-back | The update did not stick; the previous build is running again |
failed | Neither worked; the Installer is down and needs manual recovery over SSH |
If both paths fail, the helper prints the manual recovery steps: fetch the Installer's compose file
from blob storage, then docker compose pull && docker compose up -d.
The rollback tag is applied during pre-flight and is what protects the previous image from the
Installer's own dangling-image prune. Do not remove images tagged rollback by hand.
It is the fixed tag rollback, applied to the image that was running immediately before the
update. Because the tag keeps the image referenced, Docker does not treat it as dangling, so the
prune that runs after a deployment does not delete it.
There is exactly one such tag: the next self-update re-points it at whatever is running at that moment. So "the rollback target" is always the build you were on before the most recent update — which is precisely the build a bad release needs to fall back to.
No — there is no automated rollback for a DataMind OS update. The Installer implements one rollback path, and it is for its own container. A stack update recreates services in place; nothing stores a previous stack definition or re-tags previous images for that purpose.
Plan accordingly: test the release on a non-production deployment first, and take a backup before you update production. See Data and backups.
The Installer tells you loudly, because that is the one state it cannot repair. The schema version is recorded before the update starts. If the update does not stick and the database has migrated since, the failure detail says the rollback landed on a changed schema and that the running build must tolerate the new schema — verify manually.
There are no down-migrations by design: reverting a schema is more destructive and less tested than the forward path. A release whose migration genuinely breaks compatibility is therefore non-rollbackable, and the honest answer is to restore from backup rather than to roll back.
Read the version endpoint, which resolves the digest of the running container — not the tag.
curl -s http://localhost:8000/api/version \ -H "Authorization: Bearer <access-token>"
{"data":{"image":"unistream.azurecr.io/delamain:prod-latest","digest":"sha256:…"}}Resolving from the running container rather than from the image tag is deliberate: a tag can be re-pointed by a pull while the old container keeps serving, so the tag would confidently name a build that is not executing. This is also why the value is cached for the process lifetime — a container's image cannot change while it runs.
If the Installer is not running in a container at all, image comes back null and the interface
shows it as a development build.
Call the self-update check. It compares the running digest with the digest published on your channel, and separately reports whether your local compose file has drifted from the published one.
curl -s http://localhost:8000/api/system/self-update-check \ -H "Authorization: Bearer <access-token>"
{
"data": {
"updateAvailable": true,
"currentDigest": "sha256:…",
"remoteDigest": "sha256:…",
"composeChanged": true,
"error": null,
"lastUpdate": { "status": "completed", "detail": null, "at": "2026-09-29T…", "targetDigest": "sha256:…" }
}
}For DataMind OS services, GET /api/deployment/update-check does the same job per service and
reports, for each one, whether the image is present locally and whether the remote digest has
moved.
It means your on-disk compose file differs from the one published for your channel. The check
normalises line endings and compares the two, reporting composeChanged.
It matters twice: it is your signal that a hand-edit will be replaced by the next update, and it is a precondition of a self-update — the switch installs the published compose file, not your copy.
If the local compose file cannot be read from inside the container, the answer is
composeChanged: null with the reason "the local compose file is outside this container", which
means undetermined, not unchanged.
The channel decides both the image tag and the compose file that the Installer tracks. It comes
from DELAMAIN_CHANNEL in the container environment; the published production compose file sets it
to prod, and the development compose file sets it to dev.
| Channel | Image tag resolved | Published compose |
|---|---|---|
prod | prod-latest | docker-compose.yml in the delamain container of blob storage |
any other channel, e.g. dev | <channel>-latest | docker-compose.<channel>.yml in the delamain-<channel> container |
If the channel cannot be determined from the running image, both the update check and the update itself refuse rather than guess.
VERSION block an update?No. The self-update pins the image tag explicitly for the switch, and a shell variable
outranks the project's .env, so an operator-pinned VERSION cannot turn the update into a silent
no-op. The value written into the switch is the tag the backend just pulled.
A DataMind OS update does regenerate the environment, but do not rely on it as the mechanism for
a configuration change. A deploy job re-downloads the compose file and the environment schema,
re-seeds configuration from it and rewrites deployment/.env, then brings services up. Because the
.env is freshly rendered, changed values are in the file the recreated containers read.
The precise, observable path for a configuration change is Apply changes, which targets exactly the services the Installer reports as stale.
Four places, in increasing order of detail:
| Source | What it gives you |
|---|---|
| The version pill tooltip | Current digest, and "last update failed" with the reason |
GET /api/system/self-update-check | lastUpdate with status, detail, timestamp and target digest |
GET /api/deployment/actions | The audit trail, including every self-update row with status and detail |
docker logs delamain-self-updater | The helper script's own output, still present after the switch |
The helper also writes a one-word outcome breadcrumb to .self-update-status in the compose
project's working directory — a note for a human reading the host, not something the Installer reads
back.
Not safely — and a self-update cannot be cancelled at all. A deployment job can be cancelled from the interface, but if the cancellation lands while compose is already recreating services the warning is explicit: some containers may have been recreated, and you should use Start to finish or Stop to remove them.
A self-update hands off to a detached helper container before the switch, precisely because it is about to kill the process that asked for the update. Once that handoff has happened there is nothing left to cancel; the helper's one-switch-one-rollback chain runs to completion.
It is reconciled, not silently forgotten. Every job is persisted with its event stream, and on
startup the Installer marks any job that was still running as failed with the message that the
Installer restarted while it was running. Job events are kept for 30 days and pruned after that;
a finished job's snapshot lives in memory for an hour and is then served from the database.
For the full runbook, including pre-flight checks and the maintenance window, see Updates. If an update or a restart has already gone wrong, Updates and startup is the page to read next.