Verifying a release
Restow is software that holds your backups and your keys, so you should be able to check that what you run is what the project built, and to see what was tested before it was published. This page shows how, and says plainly where the checks stop.
What is in a release
Section titled “What is in a release”Every release is a GitHub release in restow-backup/restow, and the images live in the GitHub container registry. A release contains:
-
Four images in two builds, each for
linux/amd64andlinux/arm64:- the full build:
ghcr.io/restow-backup/restow(api, worker, scheduler, restic, the agent downloads and the standalone restore toolrestow-restore, plus the Business and Service Provider modules) andghcr.io/restow-backup/restow-web(the Caddy edge and the web interface), - the Community build:
ghcr.io/restow-backup/restow-communityandghcr.io/restow-backup/restow-web-community, the same without any code fromee/.
All four carry the same tag and are signed with cosign. See Get started for which build to choose.
- the full build:
-
The smoke reports:
smoke-report.md(full build, amd64),smoke-report-arm64.md,smoke-report-community.mdandsmoke-report-community-arm64.md, the results of the release smoke run described below. The release notes summarise them in their Verification section. -
The agent binaries for endpoint backup:
restow-agent_<version>_<os>-<arch>for Linux and macOS on amd64 and arm64, and their license noticesrestow-agent-THIRD_PARTY_NOTICES.txt(the licenses of the agent, Go, restic and the Go modules compiled into restic). -
The agent’s signed checksum list:
agent-SHA256SUMSand its signatureagent-SHA256SUMS.sig(see Verify the agent release). -
The SBOMs:
<image>-<version>-linux-<arch>.spdx.json, one per image and architecture (eight files, for examplerestow-0.4.7-linux-amd64.spdx.jsonorrestow-web-community-0.4.7-linux-arm64.spdx.json), in SPDX format. -
The release stack:
docker-compose.ymlandenv.example, the files fromdeploy/release/that the smoke run tests against. -
The install script:
install.shand its checksum fileinstall.sh.sha256, which installs the release stack on a dedicated Linux VM (see Get started). -
SHA256SUMS(the SHA-256 of every file above: the release stack, the install script, the SBOMs, the smoke reports, the agent binaries and their license notices) andSHA256SUMS.sigstore.json(the cosign signature of that list).
The release notes also list the digests of the four multi-architecture manifests, one line each (restow: sha256:..., restow-web: sha256:..., restow-community: sha256:... and restow-web-community: sha256:...). The optional updater verifies the images it pulls against the two lines of its own build.
Docker tags
Section titled “Docker tags”- 0.x.y (beta):
:<version>and:beta. There is no:latestyet. - From 1.0.0, a stable release:
:<version>,:MAJOR.MINOR,:MAJORand:latest. - A pre-release (
-rc.N): only:<version>.
The Community images carry the same tags. For anything you care about, name the exact version in .env (RESTOW_IMAGE, RESTOW_WEB_IMAGE) instead of a moving tag, so an update happens when you decide. See Updates.
Verify an image signature
Section titled “Verify an image signature”The images are signed with cosign in keyless mode. There is no long-lived signing key that could leak: the release workflow on GitHub Actions proves its identity to Sigstore through GitHub’s OIDC token and receives a short-lived certificate. Verifying checks that the signature was made by exactly that workflow, in the restow-backup/restow repository, for the tag of the version.
The issuer is always https://token.actions.githubusercontent.com. The identity for one version is exactly the release workflow at that version’s tag, here v0.4.7:
cosign verify ghcr.io/restow-backup/restow:0.4.7 \ --certificate-identity https://github.com/restow-backup/restow/.github/workflows/release.yml@refs/tags/v0.4.7 \ --certificate-oidc-issuer https://token.actions.githubusercontent.comThis is the check the optional updater runs before it pulls an image. The release notes print a looser variant that accepts the release workflow on any version tag:
cosign verify ghcr.io/restow-backup/restow:0.4.7 \ --certificate-identity-regexp '^https://github.com/restow-backup/restow/\.github/workflows/release\.yml@refs/tags/v' \ --certificate-oidc-issuer https://token.actions.githubusercontent.comRun the same command for restow-web, and for restow-community and restow-web-community if you use the Community build. A signature that is missing, made by another workflow or for another tag (with the exact identity), or made for other content makes cosign exit with an error. The release pipeline signs with cosign 3.1.3, so use a current cosign.
To verify exactly the manifest named in the release notes, put its digest instead of the tag: ghcr.io/restow-backup/restow@sha256:<digest> with the same two options. The signature is made over that manifest.
Verify the SBOM
Section titled “Verify the SBOM”Each image has a software bill of materials in SPDX format for each architecture, generated from the image with syft. You get it twice:
- as the file
<image>-<version>-linux-<arch>.spdx.jsonon the GitHub release (for examplerestow-0.4.7-linux-amd64.spdx.json), for reading, scanning and archiving, and - as a cosign attestation of type
spdxjsonattached to the image of that architecture, so it is tied to the image by a signature and not only by a file name.
The attestation is attached to the image of each architecture (its own digest), not to the multi-architecture tag. docker buildx imagetools inspect ghcr.io/restow-backup/restow:0.4.7 lists the digest of each platform. The flags below come from the release workflow (--type spdxjson is the type it attests with, the identity options are the ones used for the signature). The command itself has not been run against a published release, so check the cosign documentation for verify-attestation if it behaves differently in your version:
cosign verify-attestation --type spdxjson \ ghcr.io/restow-backup/restow@sha256:<digest of the linux/amd64 or linux/arm64 image> \ --certificate-identity https://github.com/restow-backup/restow/.github/workflows/release.yml@refs/tags/v0.4.7 \ --certificate-oidc-issuer https://token.actions.githubusercontent.comVerify the checksums
Section titled “Verify the checksums”SHA256SUMS lists the SHA-256 of the release files: the agent binaries and their license notices, the SBOMs, the smoke reports, docker-compose.yml, env.example, install.sh and install.sh.sha256. It is signed keyless with cosign, and SHA256SUMS.sigstore.json is that signature as a Sigstore bundle. Download both files next to the files you want to check, then verify the signature of the list and the files against the list. The --bundle option is the counterpart of the --bundle the release workflow signs with, and the identity options are the same as above:
cosign verify-blob SHA256SUMS \ --bundle SHA256SUMS.sigstore.json \ --certificate-identity https://github.com/restow-backup/restow/.github/workflows/release.yml@refs/tags/v0.4.7 \ --certificate-oidc-issuer https://token.actions.githubusercontent.com
sha256sum -c --ignore-missing SHA256SUMS--ignore-missing skips the files you did not download. The agent binaries in the release are the same ones that sit inside the smoke-tested image.
Verify the agent release
Section titled “Verify the agent release”The endpoint agent has a signature of its own, independent of GitHub and Sigstore. Every agent release (the agent, restic and the license notices THIRD_PARTY_NOTICES.txt, for every platform) is signed by the maintainer with an Ed25519 key, in the OpenSSH signature format (ssh-keygen -Y sign, namespace restow-agent-release), over the agent’s checksum list. The public key is agent/release-signing.pub in the repository at the release tag; it is compiled into the agent and written into the install scripts your instance serves.
The checksum list and its signature are on the GitHub release as agent-SHA256SUMS and agent-SHA256SUMS.sig, and every instance serves them at /install/agent/<version>/SHA256SUMS and /install/agent/<version>/SHA256SUMS.sig. With OpenSSH 8.1 or later:
printf 'restow-agent-release %s\n' "$(cut -d ' ' -f 1-2 release-signing.pub)" > allowed_signersssh-keygen -Y verify -f allowed_signers -I restow-agent-release -n restow-agent-release \ -s agent-SHA256SUMS.sig < agent-SHA256SUMSssh-keygen -l -f release-signing.pub prints the key’s fingerprint. It must be SHA256:2LB7RPpIS0wqbcZ2NA8MjuQN7ZfFPqBX3CwrGFSu76o (ED25519, restow-agent-release), the same value the release notes publish. The list names each file as <os>-<arch>/restow-agent, <os>-<arch>/restic or <os>-<arch>/THIRD_PARTY_NOTICES.txt; compare the SHA-256 of a downloaded file with its line.
The install script runs the same check before it runs anything: it verifies the signature with ssh-keygen -Y verify (or OpenSSL 3), then the SHA-256 of the agent, restic and the license notices, and prints the fingerprint of the release key it checked against (signature: OK (release key SHA256:...)). Compare it with SHA256:2LB7RPpIS0wqbcZ2NA8MjuQN7ZfFPqBX3CwrGFSu76o. The agent refuses an update that is unsigned or signed with another key. A machine without either tool (for example RHEL, Rocky or Alma 8, or Amazon Linux 2) can be installed after you check the list on another machine and pass its SHA-256 to the installer as RESTOW_SHA256SUMS_SHA256. See Endpoint backup: install.
What the release smoke run checks
Section titled “What the release smoke run checks”Before anything is published, a script starts the exact images that are about to be released from the release stack (deploy/release/docker-compose.yml) on a fresh runner, on amd64 and on arm64, for the full build and for the Community build, and runs the checks below. The release is gated on it: if a check fails, nothing is published. The same script runs nightly on main without publishing anything, and you can run it yourself (pnpm smoke in the repository). The report of each run is attached to the release. The checks:
- Install. The image carries its metadata (version, revision, platform), restic,
restow-restoreand the agent downloads with their checksums. The release compose file starts on an empty database with every migration applied. When an earlier release exists, a stack of the previous images with a tenant and a backup is switched to the new images. At the end the api is restarted on the populated database and changes nothing. - Health.
/healthzand/readyzanswer, the edge answers over HTTPS, worker and scheduler have registered, and no container keeps restarting. - Passkey sign-in. First-run setup, then in a real browser: emergency sign-in, registering a passkey on a virtual authenticator, signing out and in again with the passkey, creating a tenant in the wizard, and every screen in English and German without a missing translation.
- Microsoft 365 against a developer tenant. It runs only when credentials for a Microsoft 365 developer tenant are configured as repository secrets, never against a customer tenant, and otherwise says “skipped” in plain words.
- IMAP backup and restore. A mailbox with 24 messages in two folders on a real IMAP server, a backup, three more messages and an incremental backup, a restore next to the originals with every restored message compared by SHA-256, and the restore check green.
- Journal receipt and chain check. A synthetic Exchange journal report over SMTP, the archive item present, the hash chain verified and continued by a second report, an unknown journal address refused, and the archive export (an EML ZIP with
MANIFEST.csvandSHA256SUMS) holding the originals byte for byte. - Standalone restore.
restow-restorefrom the image, with the server stopped and no network in its container, restores the storage of check 5 and every file matches. A copy with one damaged byte is refused. - Storage targets. S3 (Garage), a local path and a bind-mounted directory standing in for an NFS or SMB share each get a write (a backup), a read (the restore check) and a scrub.
- Scans. Trivy on the image and
pnpm auditfor high and critical advisories. Critical findings that have a fix fail the run. Critical findings without a fix are listed in the report, not hidden. - Endpoint backup and restore. The real install script installs the agent in a Linux container from the running stack, enrolls it with a one-time token, backs up a folder, and a restore task puts it into a new directory, where every file matches by SHA-256. This check runs in a Linux container only: the macOS installer and agent are not part of the release checks.
- Mail import and export. EML files and an MBOX in the server-side import folder and an MBOX uploaded in pieces become one imported mailbox. It is exported as an EML ZIP and as MBOX files. The EML files come back byte for byte, every message by Message-ID, and both checksum lists agree with the files. See Mail file import and export.
- File share backup (since 0.4.0). The mounter that new installations start by default, a Samba server in a container and, for copies, a second share. The first share holds files in folders, a file name with non-ASCII characters, a file with an explicit ACL and a file the account may not read. Steps: the connection test reads the share and its permissions; a share job with a schedule and one excluded file type; a backup that saves everything except the unreadable file, which ends as a warning for that file; browsing and a catalog search; a ZIP download of two folders compared by SHA-256; the restore check, green; a second backup after a change and a deletion; a restore into a new folder with the files compared by SHA-256 and the explicit ACL entry compared; a restore with “Keep both” into the original location; History and the dashboard count the share and its runs; a
mirrorcopy job into a folder of the second share (a file deleted on the source is gone in the target, and a file outside the folder is untouched); a mirror into a share root is refused; a wrong password is classified asshare.auth_failedand the right one works again; an emptied share is not backed up as empty (share.empty_source) and its restore points stay. The api and the worker must not restart during the whole check. See File shares. - Archive capture (since 0.4.4). An IMAP account on a real IMAP server (Dovecot) with two messages in the inbox from before the archive, a tenant, and a mail job with the archive on, IMAP archiving and forwarded copies. Steps: the capture service activates the account, and the one-time takeover brings in the two old messages, flagged as captured afterwards; a new message reaches the archive within seconds through IMAP IDLE (capture way
imap_sync); the API issues the account’s archive address once; the same message forwarded at transport becomes an item that names the synced item it supersedes (smtp_forward); a copy to the account’s address and to the tenant’s journal address in one SMTP transaction is one item with one further capture, and the account shows as receiving; the hash chain verifies over every captured item. The capture ways are Business modules, so the Community build skips this check. See Archiving.
What the smoke run does not do
Section titled “What the smoke run does not do”The report states each of these, and none is ever shown as a pass. The numbers below are read from the smoke reports attached to the current release, 0.4.7 (29 October 2026); the report of each run is attached to its own release. That run passed, for the full build, 12 of 13 checks in full and skipped 1 (check 4, Microsoft 365); for the Community build it passed 9 in full and skipped 4 (the reason of each is in the report attached to the 0.4.7 release). The first run, of 0.1.0 on 2026-10-01, passed 9 of 11 in full, 1 partly (check 1, whose upgrade path had no earlier release to start from) and skipped 1 (check 4).
- Microsoft 365 against a real tenant. Check 4 has not been run against a real tenant yet; it was skipped for lack of credentials. Microsoft 365 backup and restore are covered by tests against a simulated Graph API only. The code path is implemented from the API and the documented Graph behaviour, and details are expected to need fixing on its first real run. This holds for the handling of disabled, unlicensed and locked accounts, OneDrives and sites (since 0.4.7) too, and the exclusion of Proxmox VE guests (since 0.4.7) was not run against a real Proxmox VE node. This is the main reason to run the beta alongside your existing backups.
- Only the upgrade from the previous release. The upgrade step of check 1 starts the previous release’s images with a tenant and a backup and switches them to the new ones. It had no earlier release to start from for 0.1.0 and reported “not run”; it ran first for 0.2.0 and has passed in every run since, the 0.4.7 run included. It does not try every older version: coming from 0.2.x, read the upgrade notes of 0.3.1.
- Archive capture only against Dovecot and the receiver itself. Check 13 runs IMAP archiving against Dovecot and forwarded copies against the journal receiver. Graph capture is not part of the smoke run: it was tested against a simulated Graph API only, like the rest of Microsoft 365 (check 4 is skipped). No real Microsoft 365 or Google Workspace tenant, Postfix, mailcow or hoster was involved. See the known issues of the capture ways.
- File shares only against Samba. Check 12 runs against Samba, over SMB. NFS is not part of the smoke run (an NFS server in a container needs a privileged kernel server that CI runners do not provide reliably), and file shares were not tested against Windows Server (NTFS permissions, Backup Operators, a file kept open in Excel), a Synology or TrueNAS share, or a host with SELinux enforcing. See File shares: troubleshooting and limits.
- Journal items are not compared with their source. The check reads the journal address of a tenant from
GET /api/v1/archive/journal, and no API serves the raw bytes of an archived item, so the export is the witness that the originals come back unchanged. - Critical findings without a fix. The scan cannot act on criticals the distribution has not fixed (for example in the Debian base image). They are listed, not counted as failures.
- Windows and macOS. There is no Windows agent in 0.4.7. The endpoint check (check 10) ran the real install script in a Linux container only. The macOS installer and agent were not part of the release checks; the agent’s own tests ran natively on macOS.
What CI checks on every pull request
Section titled “What CI checks on every pull request”Every pull request and every push runs, and a release re-runs all of it on the tagged commit:
- lint, and a guard that the core never imports from the
ee/modules, - type checking, unit tests and the tests against a real PostgreSQL, with a guard that none of the PostgreSQL suites was skipped,
- i18n completeness: every key of the English translation exists in German and the other way round,
- the full build, and a Docker build of both images with a look inside (restic,
restow-restore, the agent checksums, the image labels), pnpm auditfor high and critical advisories,- dependency licenses against an allowlist, and third-party notices that are up to date,
- a secret scan over the history (gitleaks),
- the Go agent: formatting,
go vet, race-detector tests, ShellCheck, integration tests against the real restic and an append-only REST server, the install script, and the cross-builds for Linux and macOS, - ShellCheck for every shell script, and a linter for the workflows.
The pipeline uses only the GITHUB_TOKEN of the run: no personal access token and no long-lived signing key. Every third-party action is pinned to a commit, and every container image a step runs is pinned by digest.
Where the checks stop
Section titled “Where the checks stop”Being honest about the gaps matters more than a long list of green checks:
- The first agent install trusts your instance. The install script comes from your own Restow instance (as with any
curl | sh), and it carries the release key it checks against. Compare the fingerprint it prints withSHA256:2LB7RPpIS0wqbcZ2NA8MjuQN7ZfFPqBX3CwrGFSu76o, the one published in the release notes. From then on, the agent accepts only updates signed with the key compiled into it. - The agent binaries carry no operating-system code signature. The release signature above covers them; there is no Apple code signature or notarization.
- The first image you start is checked by you. The optional updater verifies the cosign signature of every image it installs, but its own image (
RESTOW_UPDATER_IMAGE) and the images of your first install are the ones you check by hand, as shown above. The update check only reads the release list. - Source mode is not verified. An update built from a repository (the updater’s source mode, off by default) checks only the allowlist and
https, not the code. See Updates. - A signature is not a review. It proves which workflow built an image from which tag. It does not replace reading the release notes and testing a restore of your own data.