Skip to content

Updates

Restow ships as Docker images, one build per release, with database migrations applied automatically. There are two ways to update:

  • By hand, with docker compose. This is the default and always works. The next sections start there.
  • From the web interface, under Settings → Updates. Restow tells you when a newer version exists and, if you opt in to the updater service, installs it for you: a countdown for everyone who is signed in, a database backup first, and an automatic rollback when the new version does not start.

Nothing on this page happens unless you switch it on. A fresh installation does not look for updates, does not contact any server for that purpose, and does not run the updater.

  1. Read the release notes of every version between yours and the new one, especially Breaking changes and Update effort. Each release states its update effort (see the three kinds below), with the expected downtime and how to roll back.

  2. Back up the database. It holds metadata, the job queue and the audit log. Backed-up content lives in the chunk store and is not touched by an update.

    Terminal window
    docker compose exec -T postgres pg_dump -U restow -Fc restow > restow-$(date +%F).dump

    The updater does this itself (see below).

  3. Check that RESTOW_MASTER_KEY is backed up offline. An update never changes it, but without it no backup can be read. See Backing up Restow itself.

Only values in .env change (a new optional variable, a changed setting).

Terminal window
docker compose up -d

up -d recreates the containers whose configuration changed. docker compose restart is not an update: it restarts the running containers with their old image and their old environment, and never picks up a new image or a changed .env.

The usual case. Restow applies database migrations itself: the api container runs them as the database owner (DATABASE_MIGRATION_URL) before it serves any request, and refuses to start if one fails. The release notes say whether there are migrations and roughly how long they take.

Release stack (the published images, deploy/release/docker-compose.yml). Name the version in .env and pull:

.env
RESTOW_IMAGE=ghcr.io/restow-backup/restow:X.Y.Z
RESTOW_WEB_IMAGE=ghcr.io/restow-backup/restow-web:X.Y.Z
Terminal window
docker compose pull
docker compose up -d

If the updater ever wrote RESTOW_IMAGE and RESTOW_WEB_IMAGE into .env, those two lines decide which images the services run. Change them, or remove them to go back to building from a source checkout.

Source checkout (the repository’s own docker-compose.yml, which builds from source):

Terminal window
git fetch --tags
git checkout vX.Y.Z
docker compose up -d --build

Then watch the migrations and the start:

Terminal window
docker compose logs -f api

The log shows restow: applying database migrations, then restow: starting role 'api'.

A release that needs anything beyond the commands above (a new required variable, a changed Compose file, a one-off command) lists every step, in order, under Update effort and Breaking changes. Do them in that order, before or after docker compose up -d exactly as written. The updater cannot know about such steps: it applies the image and restarts the services. Read the notes first, and update by hand when they list anything else.

Terminal window
curl -fsS http://127.0.0.1:3000/healthz
curl -fsS http://127.0.0.1:3000/readyz
docker compose ps

Then check the version in the sidebar footer of the web interface, and run Verify now for one tenant: a restore check that passes proves the new version can still read what the old one wrote.

The tab is visible to every provider administrator. It shows the running version, the release channel, the newest version with its tag and release date, a link to the release notes (and the notes themselves, rendered safely and collapsed), the status of the last check, and whether an update is available.

Who may change it. Changing any setting, Check now, announcing, cancelling and dismissing an update need the Owner role of the provider team. The administrator created by the setup wizard is an owner. Everyone else sees the status only. See Team and roles.

The check is off by default. When an owner turns it on (Check for updates once a day), Restow reads the release list of the update source once a day and whenever you press Check now. After a failed check it tries again after an hour at the earliest.

What is read. Only the release list of the source. No request carries any data about your installation, and the result is cached. As with any web request, the source sees the address your server connects from. If a check fails, the tab says why and keeps showing the last good result:

Reason Meaning
Rate limited The source refused more requests for now. The tab shows when it lifts.
Token rejected The access token is wrong, expired or revoked.
Not found The repository does not exist, or it is private and no token is stored.
Access forbidden The token may not read the releases of that repository.
Source error, no answer The source answered with a server error, timed out or could not be reached.
Not a release list The address answered, but not with releases.
No release published The source has no release for this channel yet.
Redirect refused The source redirected to another host. Restow does not follow it, enter the final address of the repository instead.
  • Source. The default is the public releases of https://github.com/restow-backup/restow. You can point it at your own repository: a GitHub repository, or a Forgejo or Gitea repository (their releases API is compatible), for example https://git.example.com/acme/restow. Only https addresses are accepted, without credentials in the URL, because an access token travels with the request.
  • Channel. Stable offers final releases only. Beta also offers pre-releases such as 0.3.0-rc.1. Versions are compared as Semantic Versions, so a pre-release precedes its release. A release such as 0.1.0 is an ordinary release that the stable channel offers as well.
  • Access token. For a private repository, store a read-only access token. It is kept encrypted in the database, sent only as an Authorization header to the source’s own host, never shown again (the tab only says whether one is stored, and lets you replace or remove it), and never written to a log or the audit log. If you point the source at another host, the stored token is removed instead of following you there. A private repository is built from source (see below).

RESTOW_UPDATE_CHECK_URL and its precedence

Section titled “RESTOW_UPDATE_CHECK_URL and its precedence”

RESTOW_UPDATE_CHECK_URL is an environment override that predates the tab and keeps working. From strongest to weakest:

  1. RESTOW_UPDATE_CHECK_URL, when set to an https address. The check is on, the source is that address, and the tab shows it read-only. No stored token is sent to it. Accepted: a GitHub https://api.github.com/repos/<owner>/<repo>/releases (or .../releases/latest), a Forgejo or Gitea .../api/v1/repos/<owner>/<repo>/releases, or any other https address that returns the same JSON. The channel can still be changed in the tab.
  2. The settings of the tab (switch, source, channel, token).
  3. The defaults: off, the public project releases, stable.

Once per new version, Restow raises an Update available alert: an entry in the notification bell of the provider administrators, and the alert rules under Alerts & reports that list the event Update available (e-mail, webhook) in every tenant. Only provider administrators can create a rule for this event, a tenant’s own administrators are not told about your updates. The same version never raises it twice. See Alerts and reports.

GET /api/v1/status carries a version object, so an RMM can flag installations that have fallen behind: running, latest, updateAvailable (null when not known), releaseUrl, updateCheck (disabled, pending, ok or failed), checkedAt, channel, latestTag, publishedAt, checkError (the reason code of a failed check) and maintenance (an update that is announced or running: phase, targetVersion, startsAt). Fields have only ever been added, never renamed or removed. See Integration API.

The Updates tab shows Install update only when the updater service is running. Without it, the tab shows the manual steps of this page instead.

  1. Set the absolute path of the directory that holds docker-compose.yml and .env in .env. The updater mounts it at the same path, so relative paths in the Compose file resolve as they do on the host.

    Terminal window
    RESTOW_PROJECT_DIR=/opt/restow
  2. Start the updater next to the rest of the stack:

    Terminal window
    docker compose --profile updater up -d
  3. Open Settings → Updates. The install card says whether the updater is ready or what blocks it: Docker not reachable, the Docker command line image not pulled yet, Compose file not found, the project directory not matching RESTOW_PROJECT_DIR, .env not writable, or not enough free space. The first start pulls the Docker command line image (see below), which needs access to Docker Hub. Until then the card says it is preparing.

To remove it again: docker compose --profile updater rm -sf updater. The updater’s volume (restow-updater, holding the database dumps) stays until you remove it with docker volume rm.

The updater is not updated by an update. It keeps running the version it started with. After an update, recreate it so it matches: docker compose --profile updater up -d updater. The tab reminds you when its version differs from the running one.

Restow picks the mode from the update source and shows it in the tab:

  • Image (the default source, the project’s public releases). Pulls ghcr.io/restow-backup/restow:<version> for the application and ghcr.io/restow-backup/restow-web:<version> for the web edge. It verifies the pulled images against the digests the release publishes in its notes (the project’s releases do), writes the two image references into .env (RESTOW_IMAGE, RESTOW_WEB_IMAGE) and recreates the services. If a release has no web image, the web edge stays as it is and the run says so. A release that publishes no digests is still installable, and the run records that the image was not verified. The updater checks these digests, it does not verify the cosign signature: see Verifying a release if you want that step.
  • Source (any other repository, for example your own Forgejo). Downloads the tagged archive of the repository, with the stored token in an Authorization header (never in a URL, a command line or a log), builds the application and web images locally with docker build, then recreates the services the same way. This takes longer than a download.

The owner picks the version and a lead time (immediately, 1, 5, 15, 30 minutes or 1 hour) and confirms. From then on every signed-in person, not only administrators, sees a banner with a live countdown, and a notice when it is announced. The owner can cancel until the update starts. At the start, everyone sees a full-screen notice with the steps and the progress.

  1. Prepare. Checks that Docker answers, the Compose file is there, .env is writable, there is free space, the target is newer than the running version, and that the Compose file takes the image of api, worker, scheduler and web edge from RESTOW_IMAGE and RESTOW_WEB_IMAGE (so a mixed-version installation cannot result).
  2. Download or build. Nothing is stopped yet: a failure here changes nothing.
  3. Database backup. pg_dump -Fc of the database into the updater’s volume, checked for readability. The last three are kept, plus the one a run that needs attention depends on.
  4. Stop worker and scheduler. Jobs in the queue resume after the restart.
  5. Start the new version. The api is recreated with the new image and applies its database migrations as it starts.
  6. Health check. Waits until the api reports ready and the new version (it gives up at once when the api container keeps restarting), then starts worker and scheduler, then recreates the web edge last.
  7. Done.

While the api restarts, the edge answers browsers with a static maintenance page (in the browser’s language, light or dark) and answers API calls with a 503. The web interface treats that as “the server is restarting” and reloads by itself once the new version answers.

State lives in the updater’s volume, so it survives the api restarting. The result of every run also appears in the notification bell.

The updater never guesses. It ends a failed run in exactly one of three states:

  • Unchanged. It failed before anything was stopped (Docker unreachable, the image could not be pulled or did not match its digest, the build failed, the backup failed). The old version never stopped.
  • Rolled back. The new version did not come up and the database migrations had not run (the updater stops the new api first, then compares the number of applied migrations with the one before the update). The previous images are running again, .env is restored byte for byte, and the tab says which step failed and why.
  • Needs attention. The new api failed after its migrations ran. Migrations cannot be undone, so the updater does not start the old version on top of a migrated database. It stops api, worker and scheduler, keeps the database dump and tells you what to do. The web edge keeps showing the maintenance page.

If the updater itself is restarted or killed in the middle of a run, the run is recorded as interrupted (unchanged when it had not stopped anything yet, otherwise needs attention) and nothing is started on its own.

For needs attention, restore the dump taken before the update. The tab shows the exact file name and the previous image references:

Terminal window
# copy the dump out of the updater's volume
docker compose --profile updater cp updater:/state/dumps/<file> ./<file>
# stop the application and restore
docker compose stop api worker scheduler
docker compose exec -T postgres pg_restore -U restow -d restow --clean --if-exists < <file>
# put the previous images back into .env (RESTOW_IMAGE, RESTOW_WEB_IMAGE), then
docker compose up -d

Backups made after the update are in the chunk store but not in the restored database, so run a backup after the rollback. Then report the failure with the log tail the tab shows. Dismiss clears a finished run from the page. Use it only after you have restored the installation, because the recovery commands disappear with it. The run stays in the audit log.

Every step that matters is written to the audit log: update.check, update.settings.updated, update.scheduled, update.cancelled, update.started, update.succeeded, update.failed and update.acknowledged (dismissing a finished run). The updater has no database access of its own, so it keeps a journal and the api writes it to the audit log afterwards, in order and once, even when the api was replaced in between. See Audit log.

The Compose service sets what it needs. These variables are read by the updater only (ROLE=updater):

Variable Default Meaning
RESTOW_UPDATER_PROJECT_DIR required, set from RESTOW_PROJECT_DIR Absolute host path of the Compose project, mounted at the same path.
RESTOW_UPDATER_IMAGE_REPOSITORY ghcr.io/restow-backup/restow Application image repository for image mode.
RESTOW_UPDATER_WEB_IMAGE_REPOSITORY ghcr.io/restow-backup/restow-web Web image repository for image mode.
RESTOW_UPDATER_HEALTH_TIMEOUT_SECONDS 600 How long to wait for the new api (migrations can take a while).
RESTOW_UPDATER_MIN_FREE_MB 1024 Free space required in the updater’s volume.
RESTOW_UPDATER_CLI_IMAGE docker:27-cli Image used to run docker and docker compose (see below).
RESTOW_UPDATER_SOURCE_HOSTS empty (every https host) Optional comma-separated allowlist of hosts a source archive may be downloaded from.

The published image contains no Docker command line. The updater therefore runs each docker and docker compose command in a short-lived helper container of RESTOW_UPDATER_CLI_IMAGE (by default from Docker Hub, pulled in the background when the updater starts; the updater cannot install anything before that and says so), through the mounted socket, without network access and with the project directory and the updater’s state volume mounted. If a docker binary is present in the updater’s own image, it is used directly instead. The helper containers carry no registry credentials: the images of the project’s public releases need none, and a private registry is not supported in this mode. The api reaches the updater at RESTOW_UPDATER_URL (default http://updater:8090). Nothing answers there unless the updater profile runs.

  • No migrations in the release: check out (or pull) the previous version and run docker compose up -d again. The updater does this by itself when a new version does not start.

  • With migrations: migrations are not reversible. Stop the stack, restore the database dump taken before the update, then start the previous version:

    Terminal window
    docker compose stop api worker scheduler
    docker compose exec -T postgres pg_restore -U restow -d restow --clean --if-exists < restow-YYYY-MM-DD.dump
    git checkout vPREVIOUS
    docker compose up -d --build

    With the release stack, put the previous RESTOW_IMAGE and RESTOW_WEB_IMAGE back into .env instead of the git checkout, and use docker compose up -d. Backups taken after the update are in the chunk store but not in the restored database, so run a backup after the rollback.

Restow uses Semantic Versioning. Before 1.0.0, a minor version (0.2.0) adds features and may carry migrations. A patch version (0.1.1) fixes problems and carries migrations only when a fix needs one. Tags are vMAJOR.MINOR.PATCH, and pre-releases are marked -rc.N. The stable channel of the update check offers releases only, the beta channel also offers the pre-releases. The Docker tags of a release are a separate matter, see Verifying a release.