Skip to content

Get started as an administrator

This part of the documentation is for the person who installs, configures and operates Restow for an organization, or, in the Service Provider edition, for several tenants. If you are looking after only your own mailbox, see For end users → Get started instead.

Getting a running installation is two steps, covered on this page: requirements, then install. After that, Setup wizard walks through the guided first run, and First steps covers connecting a source and proving your first backup.

Restow runs as one application image with three roles (api, worker and scheduler), a web image with the Caddy edge, and PostgreSQL 16, all defined in one Compose file. You need:

  • A Linux host with Docker Engine and the Docker Compose plugin (the docker compose command, not the older standalone docker-compose v1). The release images are built for amd64 and arm64.
  • Enough disk space for PostgreSQL’s data volume and, if you use the default local storage target, the chunk store (STORAGE_LOCAL_PATH, /data/chunks inside the container). Storage needs grow with how much data you protect and how long you retain it, reduced by deduplication within a tenant; an S3-compatible target or a mounted NFS/SMB share moves that growth off the Restow host. See Backups and schedules.

Restow 0.1.0 is a public beta: test it before you rely on it, and keep an independent backup alongside it while it is new. You install it from the published release images ghcr.io/restow-backup/restow and ghcr.io/restow-backup/restow-web (signed, with an SBOM), or build it from source with the repository’s own Compose file; both ways are under Install, below. Node.js 22 and pnpm 9 are only needed to develop Restow, not to run either stack.

See the 0.1.0 release notes for what shipped and what is still known-incomplete. The source code is on GitHub.

  • Public mode (an installation reachable from the internet): a domain name with A and/or AAAA records pointing at the server’s IP address, and inbound ports 80 and 443 reachable from the internet. Caddy obtains and renews a Let’s Encrypt certificate automatically for the domain you set as RESTOW_APP_DOMAIN, with no manual certificate handling.
  • Local mode (reachable only over IP or localhost): no domain and no inbound internet access needed. This is the development/evaluation mode; passkeys and Entra SSO for end users are not offered in it (see Setup wizard).

Operating mode is chosen once in the setup wizard and can be changed later in Settings.

Only 80 and 443 (Caddy) need to be reachable from outside the host in public mode; the release stack binds the api container’s own port to loopback only and publishes no PostgreSQL port (the source stack binds PostgreSQL to loopback). Servers and clients backed up with the Restow agent reach the same address over HTTPS; they need no other port. The full table is in Reference → Ports and roles.

Restow does not yet publish official sizing benchmarks (early beta), and Microsoft Graph’s own throttling is usually the limiting factor for how fast a first backup of a large tenant completes (it can take days rather than hours; Restow shows the wait, it does not hide it). What’s true by design:

  • PostgreSQL holds metadata, the job queue and the audit log, not the backed-up content itself, which lives in the chunk store.
  • Worker throughput is configurable: WORKER_CONCURRENCY (parallel jobs per queue in one worker process) and WORKER_TENANT_CONCURRENCY (parallel jobs per tenant), both default 2. Run more than one worker container to scale further.
  • Storage grows with retained, deduplicated, encrypted data; a weekly sample scrub and a monthly full scrub check pack integrity regardless of target size.

The release stack runs the published images and builds nothing. Its docker-compose.yml and env.example are in deploy/release of the repository and are attached to every GitHub release. Download the two files of the release you want into an empty directory on the server (here for 0.1.0), then create your environment file from the example:

Terminal window
mkdir -p /opt/restow && cd /opt/restow
curl -fsSLO https://github.com/restow-backup/restow/releases/download/v0.1.0/docker-compose.yml
curl -fsSLO https://github.com/restow-backup/restow/releases/download/v0.1.0/env.example
cp env.example .env

Nothing in env.example is a usable secret by itself: every value must be generated or filled in. Never commit .env or share it.

The release images are ghcr.io/restow-backup/restow (the application: api, worker and scheduler) and ghcr.io/restow-backup/restow-web (the Caddy edge and the web interface). Both carry the same tag, the release version without the leading v, and both are built for amd64 and arm64. Every release is signed and ships an SBOM; to check the signature before you start the stack, see Verifying a release.

Fill in every empty value in the sections Images, Address, Database and Secrets; the comments in the file say how. At minimum:

Variable What it is
RESTOW_IMAGE The application image with its version, for example ghcr.io/restow-backup/restow:0.1.0. Compose refuses to start without it.
RESTOW_WEB_IMAGE The web image with the same version, for example ghcr.io/restow-backup/restow-web:0.1.0.
POSTGRES_PASSWORD Password for the Postgres superuser Docker Compose creates (POSTGRES_USER, default restow).
DATABASE_MIGRATION_URL Connection string for that same owner role; runs migrations only.
DATABASE_URL The application role, subject to Row Level Security. The migration step creates this role with the name and password you give here.
DATABASE_PROVIDER_URL The installation role (BYPASSRLS), for installation-wide lookups. Created the same way.
RESTOW_MASTER_KEY 32-byte key-encryption key, base64. See the warning below.
BETTER_AUTH_SECRET Secret for the auth system’s own tokens and sessions.
RESTOW_APP_DOMAIN The bare domain Caddy serves. In public mode Caddy requests a certificate for it. Compose refuses to start without it.
RESTOW_PUBLIC_URL The exact origin browsers open, e.g. https://restow.example.com or http://localhost:5173. Sets the passkey origin, the Entra redirect URI and the journal hostname.

Generate the two secrets with:

Terminal window
openssl rand -base64 32 # RESTOW_MASTER_KEY
openssl rand -base64 32 # BETTER_AUTH_SECRET

Generate a distinct password for each database role and for POSTGRES_PASSWORD the same way, for example:

Terminal window
openssl rand -hex 16

Everything else has a documented default (shown in the comment above the variable) and can be left empty unless you need to change it; the sections of env.example marked optional work without a value. See Reference → Environment variables for the complete list.

Restow connects to PostgreSQL with three different roles, on purpose, so Row Level Security is enforced by the database and not only by application code:

  1. Owner (DATABASE_MIGRATION_URL, Compose’s POSTGRES_USER): runs the migrations and owns every table. Nothing else uses this role at runtime.
  2. Application role (DATABASE_URL): NOSUPERUSER, cannot bypass Row Level Security, owns no table. All day-to-day tenant work runs on this role; a query without a tenant pinned sees nothing, and one with a tenant pinned sees only that tenant.
  3. Installation role (DATABASE_PROVIDER_URL, BYPASSRLS): used only for work that has to see across tenants or before a tenant is known: session/API-key lookups, the tenant list, installation-wide settings and license state, the scheduler, webhook delivery, and pg-boss’s own schema.

The migration step creates the application and installation roles with the names and passwords from these two connection strings; you do not create them by hand. api, worker and scheduler each check at startup that their application role cannot bypass Row Level Security, and refuse to start if it can.

Terminal window
docker compose up -d

Compose pulls the two images on first start and builds nothing. To update later, change the two image lines in .env and run docker compose pull && docker compose up -d; see Updates.

Database migrations run automatically as part of the api container’s startup, before it serves traffic; there is no separate migration command to run by hand.

Terminal window
curl http://127.0.0.1:3000/healthz
curl http://127.0.0.1:3000/readyz

Both should answer successfully once the postgres, api, worker and scheduler containers are all up (docker compose ps). Then open RESTOW_PUBLIC_URL in a browser: Restow shows the setup wizard on first access. Its first step is the operator notice, which you must accept before anything else.

Every release image is signed with cosign (keyless, through GitHub’s OIDC identity), so you can check that an image was built by the project’s release workflow:

Terminal window
cosign verify ghcr.io/restow-backup/restow:0.1.0 \
--certificate-identity-regexp '^https://github.com/restow-backup/restow/\.github/workflows/release\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com

Verifying a release explains this, the SBOM and the signed checksum list in full.

The release stack mounts a host folder read-only into the api and worker containers at /var/lib/restow/import, for mail file import. RESTOW_IMPORT_DIR sets the host folder (default ./import next to the Compose file). Each tenant uses its own subfolder, <folder>/<tenant slug>/, which you create yourself. Restow never writes to or deletes from this folder, which must be readable by the user the containers run as. You can leave the variable empty until you need it.

Restow does not update itself. The update check under Settings, Updates is off until an administrator turns it on, and the optional updater runs only if you start it with docker compose --profile updater up -d. The updater mounts the Docker socket, which is root on the host. Read Updates before you enable either.

The repository’s own docker-compose.yml builds both images from source instead of pulling them. Get the repository onto the server, run cp .env.example .env, fill in the same values as above (leave RESTOW_IMAGE and RESTOW_WEB_IMAGE empty: Compose then builds and runs restow:local and restow-web:local), and start it:

Terminal window
docker compose up -d

The first run builds the images (Dockerfile target runtime for api/worker/scheduler, target web for Caddy), which takes a few minutes. Run docker compose up -d --build after pulling a new version. Everything else on this page applies unchanged.

The image above is the same for every edition. Community (backup, restore, the archive, endpoint backup, and mail file import and export) is free and needs no key: AGPL-3.0, no mailbox limit. Business and Service Provider unlock additional features in that same installation with an offline-verified license key, no separate download or reinstall. See License and editions for what each edition adds, what is available today and how the key is verified.