# Technical Documentation: nicotsx/zerobyte

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/nicotsx/zerobyte)

### Overview (`README.md`)

<div align="center">
  <h1>Zerobyte</h1>
  <h3>Powerful backup automation for your remote storage<br />Encrypt, compress, and protect your data with ease</h3>
  <a href="https://github.com/nicotsx/zerobyte/blob/main/LICENSE">
    <img src="https://img.shields.io/github/license/nicotsx/zerobyte" />
  </a>
  <br />
  <figure>
    <img src="https://github.com/nicotsx/zerobyte/blob/main/screenshots/backup-details.webp?raw=true" alt="Demo" />
    <figcaption>
      <p align="center">
        Backup management with scheduling and monitoring
      </p>
    </figcaption>
  </figure>
</div>

#### Join the community

[](https://discord.gg/XjgVyXrcEH)

> [!WARNING]
> Zerobyte is still in version 0.x.x and is subject to major changes from version to version. I am developing the core features and collecting feedback. Please open issues for bugs or feature requests.

<p align="center">
<a href="https://www.buymeacoffee.com/nicotsx" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
</p>

## Introduction

Zerobyte is a backup automation tool that helps you save your data across multiple storage backends. Built on top of Restic, it provides an modern web interface to schedule, manage, and monitor encrypted backups of your remote storage.

## Documentation

The official documentation website is available at [zerobyte.app](https://zerobyte.app).

It contains up-to-date setup guides, configuration reference, and usage documentation for running Zerobyte in production.

### Features

- **Automated backups** with encryption, compression, and retention policies, powered by Restic
- **Flexible scheduling** for automated backup jobs with fine-grained retention policies
- **End-to-end encryption** will ensure your data is always protected
- **Multi-protocol support** for backup from NFS, SMB, WebDAV, SFTP, or local directories

## Installation

In order to run Zerobyte, you need to have Docker and Docker Compose installed on your server. Then, you can use the provided `compose.yaml` file to start the application.

```yaml
services:
  zerobyte:
    image: ghcr.io/nicotsx/zerobyte:v0.43
    container_name: zerobyte
    restart: unless-stopped
    cap_add:
      - SYS_ADMIN
    ports:
      - "4096:4096"
    devices:
      - /dev/fuse:/dev/fuse
    environment:
      - TZ=Europe/Zurich # Set your timezone here
      - BASE_URL=http://localhost:4096 # URL you will use to access Zerobyte
      - APP_SECRET=94bad46...c66e25d5c2b # Generate your own secret with `openssl rand -hex 32`
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/lib/zerobyte:/var/lib/zerobyte
```

> [!WARNING]
> It is highly discouraged to run Zerobyte on a server that is accessible from the internet (VPS or home server with port forwarding). If you do, make sure to change the port mapping to "127.0.0.1:4096:4096" and use a secure tunnel (SSH tunnel, Cloudflare Tunnel, etc.) with authentication.

> [!WARNING]
> Do not try to point `/var/lib/zerobyte` to a network share. You will face permission issues and strong performance degradation.

> [!NOTE]
> **TrueNAS Users:** The host path `/var/lib` is ephemeral on TrueNAS and will be reset during system upgrades. Instead of using `/var/lib/zerobyte:/var/lib/zerobyte`, create a dedicated ZFS dataset (e.g., `tank/docker/zerobyte`) and mount it instead:
>
> ```yaml
> volumes:
>   - /etc/localtime:/etc/localtime:ro
>   - /mnt/tank/docker/zerobyte:/var/lib/zerobyte
> ```
>
> This ensures your configuration, encryption keys, and database persist across TrueNAS upgrades.

Then, run the following command to start Zerobyte:

```bash
docker compose up -d
```

Once the container is running, you can access the web interface at `http://<your-server-ip>:4096`.

## Configuration

Zerobyte can be customized using environment variables. Below are the available options:

### Environment variables

| Variable                  | Description                                                                                                                               | Default                |
| :------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------- | :--------------------- |
| `BASE_URL`                | **Required.** The base URL of your Zerobyte instance (e.g., `https://zerobyte.example.com`). See [Authentication](#authentication) below. | (none)                 |
| `APP_SECRET`              | **Required.** A random secret key (32+ chars) used to encrypt sensitive data in the database. Generate with `openssl rand -hex 32`.       | (none)                 |
| `APP_SECRET_FILE`         | Path to a file containing `APP_SECRET`, useful with Docker or Kubernetes secrets. Mutually exclusive with `APP_SECRET`.                   | (none)                 |
| `PORT`                    | The port the web interface and API will listen on.                                                                                        | `4096`                 |
| `RESTIC_HOSTNAME`         | The hostname used by Restic when creating snapshots. Automatically detected if a custom hostname is set in Docker.                        | `zerobyte`             |
| `GOMAXPROCS`              | Optional positive integer passed to Restic processes to limit CPU scheduler threads. Useful for reducing CPU pressure during backups.     | Restic default         |
| `TZ`                      | Timezone for the container (e.g., `Europe/Zurich`). **Crucial for accurate backup scheduling.**                                           | `UTC`                  |
| `TRUST_PROXY`             | When `true`, trust an existing `X-Forwarded-For` header from your reverse proxy. Leave `false` for direct deployments.                    | `false`                |
| `TRUSTED_ORIGINS`         | Comma-separated list of extra trusted origins for CORS (e.g., `http://localhost:3000,http://example.com`).                                | (none)                 |
| `WEBHOOK_ALLOWED_ORIGINS` | Comma-separated list of HTTP origins allowed for backup webhooks and outbound HTTP notification destinations.                             | (none)                 |
| `WEBHOOK_TIMEOUT`         | Timeout for backup webhook requests in seconds.                                                                                           | `60`                   |
| `LOG_LEVEL`               | Logging verbosity. Options: `debug`, `info`, `warn`, `error`.                                                                             | `info`                 |
| `RCLONE_CONFIG_DIR`       | Path to the directory containing `rclone.conf` inside the container. Change this if running as a non-root user.                           | `/root/.config/rclone` |
| `PROVISIONING_PATH`       | Path to a JSON file with operator-managed repositories and volumes to sync at startup.                                                    | (none)                 |

### Performance tuning

If backups use too much CPU, set `GOMAXPROCS` on the Zerobyte container and restart it:

```yaml
environment:
  - GOMAXPROCS=2
```

This limits the Go scheduler used by Restic child processes. Existing operations are not changed; the new value applies to Restic processes started after the container restart.

Other useful Restic tuning options:

- Set repository compression to `off` to reduce CPU usage, or `auto` for the usual balance. `max` can save more space but uses more CPU.
- Use repository upload/download limits to avoid saturating network links.
- Use backup schedule **Custom restic parameters** for advanced per-job flags such as `--read-concurrency 1`, `--exclude-larger-than 10G`, or `--no-scan`.

See the full guide: [Performance tuning](https://zerobyte.app/docs/guides/performance-tuning).

### Webhook and notification network policy

Backup webhooks and outbound notification destinations that can target arbitrary network hosts are restricted by `WEBHOOK_ALLOWED_ORIGINS`.

The allowlist matches exact origins only: scheme, host, and port must match. Paths are ignored, so `https://hooks.example.com/backups` allows any path on `https://hooks.example.com`, but it does not allow `http://hooks.example.com`, `https://hooks.example.com:8443`, or `https://other.example.com`.

This policy applies to:

- backup pre/post webhook URLs
- Generic HTTP notification URLs
- Gotify server URLs
- self-hosted ntfy server URLs
- custom Shoutrrr URLs that point at generic HTTP or SMTP network targets

The public ntfy.sh service and fixed-provider notification services such as Slack, Discord, Pushover, and Telegram do not need `WEBHOOK_ALLOWED_ORIGINS`.

Backup webhooks do not follow redirects. Add the final destination origin to `WEBHOOK_ALLOWED_ORIGINS` and configure that final URL directly.

Webhook headers are stored as plain text and must use one `Key: Value` header per line. `WEBHOOK_TIMEOUT` controls backup pre/post webhook request timeouts; notification delivery uses the underlying provider sender behavior.

### Provisioned resources

Zerobyte can sync operator-managed repositories and volumes from a JSON file at startup. This is useful when you want credentials or connection details to live in deployment-time configuration instead of being entered through the UI.

Provisioned resources:

- appear in the normal repositories and volumes screens
- can resolve credential fields from environment variables or `/run/secrets/*` during startup sync

The complete provisioning documentation is available at [zerobyte.app/docs/guides/provisioning](https://zerobyte.app/docs/guides/provisioning).

See `examples/provisioned-resources/README.md` for a full example.

### Simplified setup (no remote mounts)

If you only need to back up locally-mounted folders and don't require remote share mounting capabilities, you can remove the `SYS_ADMIN` capability and FUSE device from your `compose.yaml`:

```yaml
services:
  zerobyte:
    image: ghcr.io/nicotsx/zerobyte:v0.43
    container_name: zerobyte
    restart: unless-stopped
    ports:
      - "4096:4096"
    environment:
      - TZ=Europe/Zurich # Set your timezone here
      - BASE_URL=http://localhost:4096 # Change this to your actual URL (use https:// for secure cookies)
      - APP_SECRET=94bad46...c66e25d5c2b # Generate your own secret with `openssl rand -hex 32`
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/lib/zerobyte:/var/lib/zerobyte
      - /path/to/your/directory:/mydata
```

**Trade-offs:**

- ✅ Improved security by reducing container capabilities
- ✅ Support for local directories as backup sources
- ✅ Support all repository types, local and remote (S3, GCS, Azure, rclone)
- ❌ Cannot mount NFS, SMB, WebDAV, or SFTP shares directly from Zerobyte

If you need remote mount capabilities, keep the original configuration with `cap_add: SYS_ADMIN` and `devices: /dev/fuse:/dev/fuse`.

## Examples

See [examples/README.md](examples/README.md) for runnable, copy/paste-friendly examples.

## Adding your first volume

Zerobyte supports multiple volume backends including NFS, SMB, WebDAV, SFTP, and local directories. A volume represents the source data you want to back up and monitor.

To add your first volume, navigate to the "Volumes" section in the web interface and click on "Create volume". Fill in the required details such as volume name, type, and connection settings.

If you want to backup a local directory on the same host where Zerobyte is running, you'll first need to mount that directory into the Zerobyte container. You can do this by adding a volume mapping in your `compose.yaml` file. For example, to mount `/path/to/your/directory` from the host to `/mydata` in the container, you would add the following line under the `volumes` section:

```diff
services:
  zerobyte:
    image: ghcr.io/nicotsx/zerobyte:v0.43
    container_name: zerobyte
    restart: unless-stopped
    cap_add:
      - SYS_ADMIN
    ports:
      - "4096:4096"
    devices:
      - /dev/fuse:/dev/fuse
    environment:
      - TZ=Europe/Zurich
      - BASE_URL=http://localhost:4096 # URL you will use to access Zerobyte
      - APP_SECRET=94bad46...c66e25d5c2b # Generate your own secret with `openssl rand -hex 32`
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/lib/zerobyte:/var/lib/zerobyte
+     - /path/to/your/directory:/mydata
```

After updating the `compose.yaml` file, restart the Zerobyte container to apply the changes:

```bash
docker compose down
docker compose up -d
```

Now, when adding a new volume in the Zerobyte web interface, you can select "Directory" as the volume type and search for your mounted path (e.g., `/mydata`) as the source path.



## Creating a repository

A repository is where your backups will be securely stored encrypted. Zerobyte supports multiple storage backends for your backup repositories:

- **Local directories**: Store backups on local disk subfolders in `/var/lib/zerobyte/repositories/` or any other (mounted) path
- **S3-compatible storage**: Amazon S3, MinIO, Wasabi, DigitalOcean Spaces, etc.
- **Google Cloud Storage**: Google's cloud storage service
- **Azure Blob Storage**: Microsoft Azure storage
- **rclone remotes**: 40+ cloud storage providers via rclone (see below)

Repositories are optimized for storage efficiency and data integrity, leveraging Restic's deduplication and encryption features.

To create a repository, navigate to the "Repositories" section in the web interface and click on "Create repository". Fill in the required details such as repository name, type, and connection settings.

### Using rclone for cloud storage

Zerobyte can use [rclone](https://rclone.org/) to support 40+ cloud storage providers including Google Drive, Dropbox, OneDrive, Box, pCloud, Mega, and many more. This gives you the flexibility to store your backups on virtually any cloud storage service.

**Setup instructions:**

1. **Install rclone on your host system** (if not already installed):

   ```bash
   curl https://rclone.org/install.sh | sudo bash
   ```

2. **Configure your cloud storage remote** using rclone's interactive config:

   ```bash
   rclone config
   ```

   Follow the prompts to set up your cloud storage provider. For OAuth providers (Google Drive, Dropbox, etc.), rclone will guide you through the authentication flow.

3. **Verify your remote is configured**:

   ```bash
   rclone listremotes
   ```

4. **Mount the rclone config into the Zerobyte container** by updating your `compose.yaml`:

   ```diff
   services:
     zerobyte:
       image: ghcr.io/nicotsx/zerobyte:v0.43
       container_name: zerobyte
       restart: unless-stopped
       cap_add:
         - SYS_ADMIN
       ports:
         - "4096:4096"
       devices:
         - /dev/fuse:/dev/fuse
       environment:
         - TZ=Europe/Zurich
         - BASE_URL=http://localhost:4096 # URL you will use to access Zerobyte
       volumes:
         - /etc/localtime:/etc/localtime:ro
         - /var/lib/zerobyte:/var/lib/zerobyte
   +     - ~/.config/rclone:/root/.config/rclone:ro
   ```

   > **Note for non-root users:** If your container runs as a different user (e.g., TrueNAS apps), mount your config to the appropriate location and set `RCLONE_CONFIG_DIR`:
   >
   > ```yaml
   > environment:
   >   - RCLONE_CONFIG_DIR=/home/appuser/.config/rclone
   > volumes:
   >   - ~/.config/rclone:/home/appuser/.config/rclone:ro
   > ```

5. **Restart the Zerobyte container**:

   ```bash
   docker compose down
   docker compose up -d
   ```

6. **Create a repository** in Zerobyte:
   - Select "rclone" as the repository type
   - Choose your configured remote from the dropdown
   - Specify the path within your remote (e.g., `backups/zerobyte`)

For a complete list of supported providers, see the [rclone documentation](https://rclone.org/).

## Your first backup job

Once you have added a volume and created a repository, you can create your first backup job. A backup job defines the schedule and parameters for backing up a specific volume to a designated repository.

When creating a backup job, you can specify the following settings:

- **Schedule**: Define how often the backup should run (e.g., daily, weekly)
- **Retention Policy**: Set rules for how long backups should be retained (e.g., keep daily backups for 7 days, weekly backups for 4 weeks)
- **Paths**: Specify which files or directories to include in the backup

After configuring the backup job, save it and Zerobyte will automatically execute the backup according to the defined schedule.
You can monitor the progress and status of your backup jobs in the "Backups" section of the web interface.



## Restoring data

Zerobyte allows you to easily restore your data from backups. To restore data, navigate to the "Backups" section and select the backup job from which you want to restore data. You can then choose a specific backup snapshot and select the files or directories you wish to restore. The data you select will be restored to their original location.



## Authentication

Zerobyte uses [better-auth](https://github.com/better-auth/better-auth) for authentication and session management. The authentication system automatically adapts to your deployment scenario:

### Users and roles

Zerobyte does not currently provide fine-grained RBAC for backup operations. Authenticated organization members are trusted operators for repositories, volumes, backup schedules, restores, and notification destinations in their organization.

Organization roles mainly restrict organization-management and instance-management actions. For example, normal members cannot manage members, SSO settings, invitations, registration, or global users, but they can still operate backup resources. Only add users to an organization if you are comfortable with them using the storage, volume, and notification capabilities configured for the instance.

### Cookie security

- **IP Address/HTTP access**: Set `BASE_URL=http://192.168.1.50:4096` (or your IP). Cookies will use `Secure: false`, allowing immediate login without SSL.
- **Domain/HTTPS access**: Set `BASE_URL=https://zerobyte.example.com`. Cookies will automatically use `Secure: true` for protected sessions.

### Reverse proxy setup

If you're running Zerobyte behind a reverse proxy (Nginx, Traefik, Caddy, etc.):

1. **Set `BASE_URL`** to your HTTPS domain (e.g., `https://zerobyte.example.com`)
2. The app will automatically enable secure cookies based on the `https://` prefix
3. Ensure your proxy passes the `X-Forwarded-Proto` header

### Important notes

- The `BASE_URL` must start with `https://` for secure cookies to be enabled
- Local IP addresses (e.g., `http://192.168.x.x`) are **not** treated as secure contexts by browsers, so secure cookies are disabled automatically
- `TRUSTED_ORIGINS` only allows additional origins for auth-related requests. It does not disable secure cookies or make authenticated HTTP access work when `BASE_URL` is HTTPS.
- If `BASE_URL` is HTTPS, browsers will only send Zerobyte's auth cookies over HTTPS. Plain HTTP access may still show the login page, but authenticated flows will fail because no session cookie is available.

## Troubleshooting

For troubleshooting common issues, please refer to the [TROUBLESHOOTING.md](TROUBLESHOOTING.md) file.

## Third-party software

This project includes the following third-party software components:

### Restic

Zerobyte includes [Restic](https://github.com/restic/restic) for backup functionality.

- **License**: BSD 2-Clause License
- **Copyright**: Copyright (c) 2014, Alexander Neumann <alexander@bumpern.de>
- **Status**: Included unchanged
- **License Text**: See [LICENSES/BSD-2-Clause-Restic.txt](LICENSES/BSD-2-Clause-Restic.txt)

For a complete list of third-party software licenses and attributions, please refer to the [NOTICES.md](NOTICES.md) file.

## Contributing

Contributions by anyone are welcome! If you find a bug or have a feature request, please open an issue on GitHub. If you want to contribute code, feel free to fork the repository and submit a pull request. We require that all contributors sign a Contributor License Agreement (CLA) before we can accept your contributions. This is to protect both you and the project. Please see the [CONTRIBUTING.md](CONTRIBUTING.md) file for more details.

## Development (no Docker)

With Bun and Portless installed, you can run Zerobyte locally during development without Docker:

```bash
bun install
bun run dev
```

For local development, create a `.env.local` file at the repo root and override the Docker paths:

```bash
# Example
ZEROBYTE_DATABASE_URL=./data/zerobyte.db
APP_SECRET=your_app_secret_here
RESTIC_PASS_FILE=./data/restic.pass
RESTIC_CACHE_DIR=./data/restic/cache
ZEROBYTE_REPOSITORIES_DIR=./data/repositories
ZEROBYTE_VOLUMES_DIR=./data/volumes
BASE_URL=https://*.localhost
```

Notes:

- Remote mount backends (NFS/SMB/WebDAV/SFTP) rely on Linux mount tooling and `CAP_SYS_ADMIN`; on macOS they are expected to be unavailable.
- To actually run backups/repository checks, install `restic` on your machine (e.g., via Homebrew). If `restic` is not installed, the app still starts but backup operations will fail with a clear error.

---

### Overview (`examples/README.md`)

# Examples

This folder contains runnable, copy/paste-friendly examples for running Zerobyte in different setups.

## Table of contents

### Basic usage

- [Basic Docker Compose](basic-docker-compose/README.md) — standard deployment with remote mount support (includes `SYS_ADMIN` + `/dev/fuse`).
- [Simplified Docker Compose (no remote mounts)](simplified-docker-compose/README.md) — reduced-privilege deployment (no `SYS_ADMIN`, no `/dev/fuse`).
- [Bind-mount a local directory](directory-bind-mount/README.md) — back up a host folder by mounting it into the container.
- [Mount an rclone config](rclone-config-mount/README.md) — use rclone-based repository backends by mounting your rclone config.
- [Provisioned repositories and volumes](provisioned-resources/README.md) — operator-managed provisioning with env/file references kept in a mounted JSON file.

### Advanced setups

- [Tailscale sidecar](tailscale-sidecar/README.md) — run Zerobyte behind a Tailscale sidecar using shared networking.

---

### Overview (`scripts/README.md`)

# Scripts

Utility scripts for Zerobyte development and testing.

## Deploy a development agent

```bash
bun run dev:agent user@linux-server
```

Start `ENABLE_REMOTE_AGENTS=true bun run dev` first. Remote agents are disabled by default. The deploy command uses `portless get zerobyte` to identify this worktree, then resolves its running Tailscale URL from `portless list`. It fails before connecting over SSH if Portless returns no usable URL or this worktree has no active Tailscale route.

Uses your normal SSH configuration and keys, detects the remote architecture, compiles the local source with Bun, uploads it, and runs the native installer. The agent is installed at `/usr/local/bin/zerobyte-agent` and runs in the background. Each deployment refreshes the saved controller address, including its current Tailscale port. Existing identity and shared folders are preserved; the service is restarted, so deploy between backup runs.

First-time setup prompts for the connection code from the dashboard. The remote machine needs the usual agent prerequisites, including Restic and systemd, and must be able to reach the controller over Tailscale. Sudo may prompt for your password.

Use `--port 2222` for a custom SSH port. To avoid repeating the host, set `ZEROBYTE_DEV_AGENT_HOST` in your local environment or `.env.local`, then run just `bun run dev:agent`. This deploys unpublished local code; it does not push or publish a release.

## create-test-files.ts

Generates temporary test files with random content for testing Zerobyte backup functionality.

### Usage

```bash
bun scripts/create-test-files.ts [options]
```

### Options

| Option              | Description                           | Default          |
| ------------------- | ------------------------------------- | ---------------- |
| `-c, --count <num>` | Number of files to create             | 10               |
| `--min-size <size>` | Minimum file size                     | 1K               |
| `--max-size <size>` | Maximum file size                     | 1M               |
| `-o, --out <dir>`   | Output directory                      | ./tmp/test-files |
| `-n, --nested`      | Create files in nested subdirectories | false            |
| `-h, --help`        | Show help message                     | -                |

### Size Format

Sizes can be specified as: `<number>[K|M|G|T][B]`

- `100` = 100 bytes
- `10K` = 10 kilobytes
- `5M` = 5 megabytes
- `1G` = 1 gigabyte

### Examples

```bash
# Create 10 test files (default)
bun scripts/create-test-files.ts

# Create 50 files, 10K to 100K, with nested directories
bun scripts/create-test-files.ts -c 50 --min-size 10K --max-size 100K -n

# Create 5 files, 1MB to 10MB
bun scripts/create-test-files.ts -c 5 --min-size 1M --max-size 10M -o ./data/test-backup

# Create 100 small files in nested structure
bun scripts/create-test-files.ts -c 100 --min-size 100B --max-size 1K -n
```

---

### Overview (`examples/basic-docker-compose/README.md`)

# Basic Docker Compose

Minimal "standard" deployment for Zerobyte.

This setup enables remote mount backends (NFS/SMB/WebDAV) from inside the container by granting the required capability and FUSE device.

## Prerequisites

- Docker + Docker Compose

## Setup

```bash
cp .env.example .env
docker compose up -d
```

## Access

- UI/API: `http://<host>:4096`

## Notes

- This example uses `cap_add: SYS_ADMIN` and `/dev/fuse` to support mounting remote volumes.
- Do not place `/var/lib/zerobyte` on a network share.

---

### Overview (`examples/directory-bind-mount/README.md`)

# Bind-mount a local directory

This example shows how to back up a host directory by bind-mounting it into the Zerobyte container.

It uses the simplified setup (no remote mounts).

## Prerequisites

- Docker + Docker Compose

## Setup

1. Copy the env file:

```bash
cp .env.example .env
```

2. Edit `.env` and set `HOST_DATA_DIR` to the directory you want to back up.

3. Start the stack:

```bash
docker compose up -d
```

## Use in Zerobyte

- Create a new volume of type **Directory**
- Select the mounted path shown in the compose file: `/mydata`

## Access

- UI/API: `http://<host>:4096`

---

### Overview (`examples/provisioned-resources/README.md`)

# Provisioned repositories and volumes

This example shows how to keep operator-managed repositories and volumes in a mounted JSON file instead of creating them through the UI.

At startup, Zerobyte reads the provisioning file, resolves any `env://...` or `file://...` references, encrypts the resolved secrets into the database, and syncs the resources into the normal repositories and volumes lists as managed entries.

## Why this model

- Secret references stay in deployment-time config instead of the UI.
- Provisioned repositories and volumes show up in the normal UI and API.
- Secret rotation is just an env/secret update plus a restart.

## What this example includes

- `compose.yaml` mounts a provisioning file and a Docker secret.
- `.env.example` provides the environment variables used by `env://...` references.
- `provisioning.example.json` provisions one S3 repository and one WebDAV volume.
- `secrets/aws_secret_access_key.example` shows the file consumed by `file://aws_secret_access_key`.

## Prerequisites

- Docker + Docker Compose
- An existing Zerobyte organization ID (found in the UI under Settings > Organization)
- An S3-compatible repository target and a WebDAV share, or your own equivalent values

If this is a brand-new Zerobyte instance, finish first-run setup first so you have a real organization ID, then enable provisioning and restart the container.

## Setup

1. Copy the example files:

```bash
cp .env.example .env
cp provisioning.example.json provisioning.json
cp secrets/aws_secret_access_key.example secrets/aws_secret_access_key
```

2. Edit `.env`:

- Set `APP_SECRET` to a real secret, for example `openssl rand -hex 32`, or set `APP_SECRET_FILE` to a mounted secret file
- Set `ZEROBYTE_AWS_ACCESS_KEY_ID`
- Set `ZEROBYTE_WEBDAV_PASSWORD`
- Adjust `BASE_URL` and `TZ` if needed

3. Edit `provisioning.json`:

- Replace `organizationId` with your existing Zerobyte organization ID
- Update the S3 endpoint/bucket values
- Update the WebDAV server, path, and username

4. Edit `secrets/aws_secret_access_key` and replace the placeholder value with the real secret access key.

5. Start the stack:

```bash
docker compose up -d
```

## How secret references work

- `env://ZEROBYTE_AWS_ACCESS_KEY_ID` reads from a container environment variable.
- `env://ZEROBYTE_WEBDAV_PASSWORD` reads from a container environment variable.
- `file://aws_secret_access_key` reads `/run/secrets/aws_secret_access_key` inside the container.
- The resolved values are encrypted before Zerobyte stores them in the database.

`file://...` references are always resolved from `/run/secrets` and must be a single filename, not a nested path.

## Access

- UI/API: `http://<host>:4096`

## What you'll see in Zerobyte

- `AWS Production Backups` appears in the repositories list as a managed repository.
- `Team A WebDAV` appears in the volumes list as a managed volume.
- Changes to `provisioning.json`, `.env`, or mounted secret files apply on the next container restart.

## Rotating or removing provisioned resources

- Rotate an env-based secret: update `.env`, then restart Zerobyte.
- Rotate a file-based secret: update `secrets/aws_secret_access_key`, then restart Zerobyte.
- Remove a resource: add `delete: true`, then restart Zerobyte.

## Notes

- This example keeps `SYS_ADMIN` and `/dev/fuse` enabled because the sample volume uses WebDAV.
- Each provisioned entry must reference an existing `organizationId`.
- Each entry includes both a top-level `backend` and the matching `config.backend`.

---

### Overview (`examples/rclone-config-mount/README.md`)

# Mount an rclone config (for rclone repositories)

This example shows how to make an existing rclone configuration available inside the Zerobyte container.

Use this if you want to use **rclone** as a repository backend (Dropbox/Google Drive/OneDrive/etc.).

## Prerequisites

- Docker + Docker Compose
- An existing rclone config directory on the Docker host

If you don't have one yet:

```bash
rclone config
```

## Setup

1. Copy the env file:

```bash
cp .env.example .env
```

2. Edit `.env` and set `RCLONE_CONFIG_DIR` to the absolute path of your host rclone config directory.

3. Start the stack:

```bash
docker compose up -d
```

## Access

- UI/API: `http://<host>:4096`

## Notes

- This setup does not require `SYS_ADMIN` or `/dev/fuse`.
- The rclone config is mounted read-only into `/root/.config/rclone`.

---

### Overview (`examples/simplified-docker-compose/README.md`)

# Simplified Docker Compose (no remote mounts)

A reduced-privilege setup for Zerobyte when you do **not** need to mount NFS/SMB/WebDAV from inside the container.

## Prerequisites

- Docker + Docker Compose

## Setup

```bash
cp .env.example .env
docker compose up -d
```

## Access

- UI/API: `http://<host>:4096`

## Trade-offs

- ✅ No `SYS_ADMIN`
- ✅ No `/dev/fuse`
- ✅ Still supports all repository backends (local, S3, GCS, Azure, rclone)
- ❌ Cannot mount remote shares from inside Zerobyte

---

### Overview (`examples/tailscale-sidecar/README.md`)

# Zerobyte + Tailscale sidecar (Docker Compose)

This example runs Zerobyte behind a Tailscale sidecar container so the Zerobyte web UI/API can be reached over your tailnet and Zerobyte can access other devices on your tailnet (based on the ACLs/tags you configure in Tailscale).

It uses a common “sidecar networking” pattern:

- The `tailscale` container brings up a Tailscale node
- The `zerobyte` container shares the `tailscale` network namespace (`network_mode: service:tailscale`)

## About Tailscale

Tailscale is a mesh VPN built on WireGuard. It connects devices and containers into a private network (“tailnet”) without opening inbound ports on your router or exposing services directly to the public internet.

In this example, Tailscale acts as a secure access layer in front of Zerobyte:

- You reach Zerobyte using the node’s tailnet IP/DNS name.
- Access can be restricted using Tailscale ACLs/tags.

## Prerequisites

- Docker + Docker Compose
- A Tailscale account and an auth key

This example supports two Tailscale modes:

- **Kernel mode** (`TS_USERSPACE=false`, default):
  - requires `/dev/net/tun` on the host
  - requires `NET_ADMIN`
  - may require `SYS_MODULE` on some hosts (kept commented out in the compose file)

- **Userspace mode** (`TS_USERSPACE=true`):
  - does **not** require `/dev/net/tun`
  - works better on Docker Desktop / restricted hosts
  - requires a small edit in the compose file (see Troubleshooting)

## Setup

1. Copy the env file and fill in your auth key:

   ```bash
   cp .env.example .env
   ```

2. Start the stack:

   ```bash
   docker compose up -d
   ```

3. In the Tailscale admin console, confirm the node is present (and approved if your policy requires it).

## Access

- Over Tailscale: `http://<tailscale-ip>:4096` or `http://<tailscale-name>:4096` (if MagicDNS is enabled)
- Locally (optional): the example publishes `4096:4096` on the host

If you want Zerobyte to be reachable only via Tailscale, remove the `ports:` section from the `tailscale` service in [compose.yaml](compose.yaml). Zerobyte will still be able to access the internet and other resources outside the tailnet, but UI will only be accessible over Tailscale with possibility to further restrict access to it with ACLs/tags.

## Notes

- `network_mode: service:tailscale` makes Zerobyte share the Tailscale container’s *entire* network namespace (interfaces + routing table).
- Traffic to tailnet IPs (typically `100.x.y.z`) goes over `tailscale0` and is governed by Tailscale ACLs; traffic to your LAN/Internet may still go over the normal network interface depending on routes and host firewall.
- If `--accept-routes` is used, the Tailscale container may add routes to your routing table that Zerobyte will also use and be able to access remote networks.
- If `--accept-dns` is used, Zerobyte will also use Tailscale’s DNS servers.
- Zerobyte still needs `SYS_ADMIN` and `/dev/fuse` if you intend to mount NFS/SMB/WebDAV volumes from inside the container.

The example uses these environment variables (see [.env.example](.env.example)):

- `TS_AUTHKEY` (required)
- `TS_HOSTNAME` (optional)
- `TS_EXTRA_ARGS` (optional; passed to `tailscale up`)
- `TS_USERSPACE` (optional; set to `true` to use userspace mode)
- `TZ` (optional)

## Troubleshooting

- If the `tailscale` container can’t start due to missing TUN support, ensure your host has `/dev/net/tun` available and that Docker is allowed to use it.
- If your tailnet uses ACLs/tags, set `TS_EXTRA_ARGS` accordingly (for example `--advertise-tags=tag:backup`).
- If the `tailscale` container fails due to Docker Desktop / missing TUN support: set `TS_USERSPACE=true` in your `.env`, remove the `/dev/net/tun:/dev/net/tun` device mapping in [compose.yaml](compose.yaml), and keep `SYS_MODULE` disabled (commented out).

To confirm the tailnet address of the container:

```bash
docker exec zerobyte-tailscale tailscale status
docker exec zerobyte-tailscale tailscale ip -4
```

To confirm received routes when `--accept-routes` is used in kernel mode:
(Missing routes could be due to ACLs or because `--accept-routes` is not set or not supported in userspace mode)
```bash
docker exec zerobyte-tailscale ip route
docker exec zerobyte-tailscale ip route show table 52
```

---

### Overview (`packages/core/README.md`)

# core

To install dependencies:

```bash
bun install
```

To run:

```bash
bun run index.ts
```

This project was created using `bun init` in bun v1.3.10. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.

---

### Apps Docs Content Docs Index (`apps/docs/content/docs/index.mdx`)

---
title: Zerobyte Documentation
description: Backup automation platform built on Restic
---

Welcome to Zerobyte, a powerful backup automation platform built on [Restic](https://restic.net/).

## Getting Started

<Cards>
  <Card title="Introduction" icon={<BookOpen />} href="/docs/introduction">
    Learn about Zerobyte's features and capabilities
  </Card>

  <Card title="Installation" icon={<Download />} href="/docs/installation">
    Deploy Zerobyte with Docker and Docker Compose
  </Card>

  <Card title="Quick Start" icon={<Rocket />} href="/docs/quickstart">
    Set up your first backup in minutes
  </Card>

  <Card title="3-2-1 Strategy" icon={<ShieldCheck />} href="/docs/guides/3-2-1-backup-strategy">
    Learn why 3-2-1 backups matter and how to set them up in Zerobyte
  </Card>
</Cards>

## What is Zerobyte?

Zerobyte simplifies backup management by providing an intuitive web interface on top of Restic's powerful backup engine. Whether you're backing up local directories, NFS shares, or cloud storage, Zerobyte handles encryption, compression, scheduling, and retention policies automatically.

### Key Capabilities

- **Automated backups** with encryption, compression, and retention policies
- **Flexible scheduling** with cron-like expressions
- **Multi-protocol support** for NFS, SMB, WebDAV, SFTP, and local directories
- **Cloud storage** via S3, Google Cloud Storage, Azure Blob, and 40+ rclone backends
- **Snapshot browsing and restore** directly from the web interface

<Callout type="warn">
  Zerobyte is still in version 0.x.x and is subject to major changes between versions. Core features are under active development
</Callout>

## Explore the Docs

<Cards>
  <Card title="Concepts" icon={<Layers />} href="/docs/concepts/volumes">
    Understand volumes, repositories, and backup jobs
  </Card>

  <Card title="Guides" icon={<BookMarked />} href="/docs/guides/rclone">
    Step-by-step guides for rclone, reverse proxies, Tailscale, and more
  </Card>

  <Card title="Configuration" icon={<Settings />} href="/docs/configuration">
    Environment variables and Docker configuration reference
  </Card>

  <Card title="CLI Reference" icon={<Terminal />} href="/docs/cli">
    Admin commands for account recovery and maintenance
  </Card>

  <Card title="Troubleshooting" icon={<LifeBuoy />} href="/docs/troubleshooting">
    Solve common issues with permissions, mounts, and rclone
  </Card>
</Cards>

import { BookOpen, Download, Rocket, ShieldCheck, Layers, BookMarked, Settings, Terminal, LifeBuoy } from "lucide-react";

---

### Apps Docs Content Docs Quickstart (`apps/docs/content/docs/quickstart.mdx`)

---
title: Quick Start
description: Set up your first backup in minutes with Zerobyte
---

This guide walks you through creating your first backup in Zerobyte, from initial setup to verifying a successful backup.

<Callout type="info">
  This guide assumes you've already [installed Zerobyte](/docs/installation) and can access the web interface.
</Callout>

## Overview

Setting up a backup involves three key steps:

<Steps>
  <Step>

### Add a Volume

Define the source data you want to backup

</Step>

  <Step>

### Create a Repository

Set up an encrypted storage destination for your backups

</Step>

  <Step>

### Configure a Backup Job

Connect your volume to your repository with scheduling and retention policies

</Step>
</Steps>

Let's walk through each step.

## First-Time Setup

On a brand new Zerobyte install with no users yet, the app starts with onboarding:

<Steps>
  <Step>

### Open Zerobyte

Navigate to the URL you configured in `BASE_URL` during installation. This is usually `http://<server-ip>:4096`.

</Step>

  <Step>

### Create the Admin User

On the **Onboarding** page, enter:

- **Email**
- **Username**
- **Password**
- **Confirm Password**

Then click **Create admin user**.

</Step>

  <Step>

### Download the Recovery Key

After the admin user is created, Zerobyte redirects to **Download Recovery Key**.

Enter your account password in **Confirm Your Password**, then click **Download Recovery Key**.

</Step>

  <Step>

### Continue to Volumes

After the recovery key is downloaded, Zerobyte redirects to **Volumes**. The rest of this guide starts from there.

</Step>
</Steps>

<Callout type="warn">
  **Critical**: Store the downloaded recovery key file safely. It contains the Restic password required to recover your backups. If you lose both server access and this file, your backups will be unrecoverable. See [Recovery keys and repository passwords](/docs/guides/recovery-key-and-repository-passwords) for the full model, including imported repositories with custom passwords.
</Callout>

<Callout type="info">
  After onboarding is complete, future visits use the normal **Login** page instead of the onboarding flow.
</Callout>

## Step 1: Add a Volume

A **volume** represents the source data you want to backup. Zerobyte supports various volume types including local directories, NFS shares, SMB shares, WebDAV, SFTP, and rclone mounts.

### Adding a Local Directory

For this quick start, we'll add a local directory. First, ensure the directory is mounted into your Zerobyte container.

#### Mount the Directory

Update your `compose.yaml` to mount the directory you want to backup:

```yaml
services:
  zerobyte:
    image: ghcr.io/nicotsx/zerobyte:v0.43
    container_name: zerobyte
    restart: unless-stopped
    # ... other configuration ...
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/lib/zerobyte:/var/lib/zerobyte
      - /path/to/your/data:/data  # Add this line
```

Restart the container to apply changes:

```bash
docker compose down
docker compose up -d
```

#### Create the Volume in Zerobyte

<Steps>
  <Step>

### Navigate to Volumes

After logging in, you are already on **Volumes**. If you navigate away, click **Volumes** in the sidebar.

</Step>

  <Step>

### Create New Volume

Click the **Create Volume** button

</Step>

  <Step>

### Configure Volume

Fill in the volume details:

- **Name**: Give it a descriptive name (e.g., "My Documents")
- **Backend**: Leave this set to `Directory`
- **Directory Path**: Use the filesystem browser to select the mounted directory (e.g., `/data`)

</Step>

  <Step>

### Save Volume

Click **Create** to save the volume configuration

</Step>
</Steps>

<Callout type="info">
  The directory browser shows paths from the Zerobyte server or container filesystem, not your host filesystem directly. In the Docker example above, the host path `/path/to/your/data` appears in Zerobyte as `/data`, so that is the path you select in the browser.
</Callout>

### Adding Remote Volumes

Zerobyte also supports remote volume types:

<Tabs items={['NFS', 'SMB/CIFS', 'WebDAV', 'SFTP']}>
  <Tab value="NFS">
    Configure an NFS share:

    - **Server**: NFS server hostname or IP
    - **Export Path**: NFS export path (e.g., `/mnt/data`)
    - **Port**: NFS port (default: `2049`)
    - **Version**: `3`, `4`, or `4.1`
    - **Read-only Mode**: Optional, recommended for backup sources
  </Tab>

  <Tab value="SMB/CIFS">
    Configure an SMB/CIFS share:

    - **Server**: SMB server hostname or IP
    - **Share**: Share name
    - **Guest Mode**: Optional for unauthenticated shares
    - **Username**: SMB username
    - **Password**: SMB password
    - **SMB Version**: `1.0`, `2.0`, `2.1`, or `3.0`
    - **Domain**: Windows domain or workgroup (optional)
    - **Port**: SMB port (default: `445`)
  </Tab>

  <Tab value="WebDAV">
    Configure a WebDAV server:

    - **Server**: WebDAV server hostname or IP
    - **Path**: Remote path on the server
    - **Username**: WebDAV username (optional)
    - **Password**: WebDAV password (optional)
    - **Port**: Server port (`80` by default, or `443` for HTTPS)
    - **Use SSL/HTTPS**: Enable secure HTTPS connections
    - **Read-only Mode**: Optional, recommended for backup sources
  </Tab>

  <Tab value="SFTP">
    Configure an SFTP server:

    - **Host**: SFTP server hostname or IP
    - **Port**: SFTP port (default: 22)
    - **Username**: SSH username
    - **Password**: SSH password (optional if using a private key)
    - **Private Key**: SSH private key (optional if using a password)
    - **Path**: Remote directory path
    - **Known Hosts**: Required unless you enable **Skip Host Key Verification**
  </Tab>
</Tabs>

<Callout type="info">
  Remote volumes require the full installation with `SYS_ADMIN` capability and `/dev/fuse` device. See the [Installation guide](/docs/installation) for details.
</Callout>

## Step 2: Create a Repository

A **repository** is the encrypted storage destination where your backups will be stored. Zerobyte supports multiple repository backends.

### Creating a Local Repository

For this quick start, we'll create a local repository:

<Steps>
  <Step>

### Navigate to Repositories

Click on **Repositories** in the sidebar

</Step>

  <Step>

### Create New Repository

On a new install, click **Create repository** from the empty state

</Step>

  <Step>

### Configure Repository

Fill in the repository details:

- **Name**: Descriptive name (e.g., "Local Backup Repository")
- **Backend**: Select `Local`
- **Compression Mode**: `Auto (fast)` is the default and a good starting point
- **Import existing repository**: Leave this unchecked for a brand new repository

</Step>

  <Step>

### Review Repository Directory

For local repositories, Zerobyte stores data under the repository base directory, which defaults to `/var/lib/zerobyte/repositories/{unique-id}`. Click **Change** only if you want to pick a different base directory.

</Step>

  <Step>

### Create Repository

Click **Create repository**. Zerobyte creates and initializes the repository, then redirects you to the repository details page.

</Step>
</Steps>

<Callout type="info">
  If you choose a custom local repository directory, make sure it is mounted from the host into the container. Otherwise the repository data can be lost when the container restarts.
</Callout>

<Callout type="info">
  If you are importing an existing repository instead of creating a new one, read [Recovery keys and repository passwords](/docs/guides/recovery-key-and-repository-passwords) before choosing between the existing recovery key and a custom repository password.
</Callout>

### Cloud Storage Repositories

For production use, consider using cloud storage for offsite backups:

<Tabs items={['S3-Compatible', 'Google Cloud Storage', 'Azure Blob', 'rclone']}>
  <Tab value="S3-Compatible">
    Configure S3-compatible storage (AWS S3, MinIO, Wasabi, Backblaze B2, etc.):

    ```yaml
    Type: S3
    Endpoint: s3.amazonaws.com (or custom endpoint)
    Bucket: my-backup-bucket
    Access Key ID: AKIAIOSFODNN7EXAMPLE
    Secret Access Key: my-secret-access-key
    ```
  </Tab>

  <Tab value="Google Cloud Storage">
    Configure Google Cloud Storage:

    ```yaml
    Type: Google Cloud Storage
    Bucket: my-backup-bucket
    Project ID: my-project-id
    Service Account JSON: Paste the service account JSON
    ```
  </Tab>

  <Tab value="Azure Blob">
    Configure Azure Blob Storage:

    ```yaml
    Type: Azure Blob Storage
    Container: backups
    Account Name: myaccountname
    Account Key: my-account-key
    Endpoint Suffix: core.windows.net (optional)
    ```
  </Tab>

  <Tab value="rclone">
    Configure rclone-based storage (requires rclone config mounted):

    ```yaml
    Type: rclone
    Remote: gdrive (must exist in rclone config)
    Path: backups/zerobyte
    ```

    <Callout type="info">
      rclone must be configured on the host and mounted into the container. See [Installation - Mounting rclone Configuration](/docs/installation#mounting-rclone-configuration).
    </Callout>
  </Tab>
</Tabs>

<Callout type="info">
  If you want credentials to come from environment variables or Docker secrets, use the [Provisioning guide](/docs/guides/provisioning). The standard volume and repository forms currently expect the actual secret value and store it encrypted.
</Callout>

## Step 3: Create a Backup Job

A **backup job** connects a volume to a repository and defines when and how backups should run.

<Steps>
  <Step>

### Navigate to Backups

Click on **Backups** in the sidebar

</Step>

  <Step>

### Start a New Backup Job

Click **Create a backup job**

</Step>

  <Step>

### Choose the Volume

Select the volume you just created. Zerobyte then opens the backup configuration form for that volume.

</Step>

  <Step>

### Configure Basic Settings

- **Backup name**: Descriptive name (e.g., "Daily Documents Backup")
- **Backup repository**: Select the repository you created earlier
- **Backup frequency**: Choose how often the job should run

</Step>

  <Step>

### Configure Schedule

Choose the schedule that matches how you want this backup to run:

- **Manual only**: Create the job without a schedule and run it on demand
- **Hourly**
- **Daily**
- **Weekly**
- **Specific days**: Pick one or more days of the month
- **Custom (Cron)**: Enter a cron expression

If you choose **Daily**, **Weekly**, or **Specific days**, set an **Execution time**.

If you choose **Weekly**, also choose the day of the week.

<Accordions type="single">
  <Accordion title="Common Cron Patterns">
    - `0 2 * * *` - Daily at 2:00 AM
    - `0 2 * * 0` - Weekly on Sunday at 2:00 AM
    - `0 2 1 * *` - Monthly on the 1st at 2:00 AM
    - `0 */6 * * *` - Every 6 hours
    - `0 0 * * 1-5` - Weekdays at midnight
  </Accordion>
</Accordions>

</Step>

  <Step>

### Configure Retention Policy

Define how long backups should be kept:

```yaml
Keep Daily: 7
Keep Weekly: 4
Keep Monthly: 6
Keep Yearly: 1
```

<Callout type="info">
  Retention policies automatically prune old backups to save storage space. Restic's deduplication ensures you only store unique data.
</Callout>

</Step>

  <Step>

### Configure Paths (Optional)

By default, the entire volume is backed up. You can optionally:

- Use **Backup paths** to select only specific files or folders from the volume
- Add **Additional include patterns** for glob-based include rules
- Add **Exclude patterns** for files or folders to skip (e.g., `*.tmp`, `node_modules/**`)

</Step>

  <Step>

### Advanced Options (Optional)

Open **Advanced** if you need extra Restic options.

Common options include:

- **Maximum retries**: Retry failed backup runs automatically
- **Retry delay**: Wait before retrying a failed run
- **Custom restic parameters**: Pass supported Restic flags one per line

</Step>

  <Step>

### Save Backup Job

Click **Create** to save the backup job. Zerobyte redirects you straight to the backup job details page.

</Step>
</Steps>

## Step 4: Run Your First Backup

Instead of waiting for the scheduled time, let's run the backup immediately:

<Steps>
  <Step>

### Open the Backup Job Details

After creating the job, Zerobyte opens the backup job details page automatically

</Step>

  <Step>

### Run Backup Manually

Click **Backup now**

</Step>

  <Step>

### Monitor Progress

Zerobyte starts the job immediately and updates the status on the details page. A success toast appears when the run begins.

</Step>

  <Step>

### Wait for Completion

Wait for the backup to finish. When it completes successfully, the job status changes to **Success** and a new entry appears in the **Snapshots** section.

</Step>
</Steps>

<Callout type="info">
  During the first backup, all data is uploaded. Subsequent backups are much faster due to Restic's incremental backup and deduplication.
</Callout>

## Step 5: Verify Your Backup

After the backup completes, verify it was successful:

<Steps>
  <Step>

### Open the Snapshot

In the **Snapshots** section, click the snapshot you just created

</Step>

  <Step>

### Check Snapshots

You should see snapshot details such as:

- Snapshot timestamp
- Total size
- Data added vs. data stored
- Files processed
- Duration

</Step>

  <Step>

### Browse Backup Contents

Use the inline file browser to inspect the snapshot contents. You can:

- Navigate the directory structure
- View file sizes
- Confirm the expected files are present

</Step>

  <Step>

### Test Restore (Optional)

For peace of mind, test restoring a file:

- Click **Restore** from the snapshot details
- Choose **Original location** or **Custom location**
- Use **Restore All** or select specific files and restore only those
- Verify the restored file is present in the target location

</Step>
</Steps>

<Callout type="warn">
  By default, restored files are placed back in their original location. Be careful not to overwrite current files. You can specify an alternate restore path if needed.
</Callout>

## Understanding Backup Status

The UI exposes two related status views:

- In the **Backups** list, the status dot shows **Active**, **Paused**, **Error**, **Warning**, or **Backup in progress**
- In the backup job details page, the last run status shows **✓ Success**, **✗ Error**, **! Warning**, or **⟳ in progress...**
- If a backup job has not completed a run yet, the details page shows no last run value

<Callout type="info">
  Click any backup job to view its snapshot history, restore actions, schedule summary, and repository links. If a run fails and the UI does not show enough detail, check your container logs with `docker compose logs -f zerobyte`.
</Callout>

## Monitoring Your Backups

Zerobyte provides several ways to monitor backup health:

### Backup Jobs List

The **Backups** page shows:

- Backup job names
- Source volume and target repository
- Schedule
- Last backup time
- Next backup time

### Backup Job Details

Each backup job displays:

- Last run time and status
- Next backup time
- Snapshot history
- Snapshot statistics for the selected snapshot
- Restore actions for the selected snapshot

### Repository Health

Repository details show:

- Current repository status (`healthy`, `error`, `unknown`, `doctor`, or `cancelled`)
- Last checked time
- Compression statistics and snapshot count
- Doctor report output

<Callout type="info">
  Use **Run doctor** in the repository details page when you need a repository maintenance pass. Use the refresh button in **Compression Statistics** when you only need updated stored-size and snapshot metrics. For the exact meaning of the status values and actions, see [Repository maintenance and status](/docs/guides/repository-maintenance).
</Callout>

## Next Steps

Now that you have a working backup, consider:

<Cards>
  <Card title="Add More Volumes" icon={<FolderPlus />}>
    Back up additional directories or remote shares
  </Card>

  <Card title="Set Up Cloud Storage" icon={<Cloud />}>
    Configure offsite backups to S3, Google Cloud, or Azure
  </Card>

  <Card title="Configure Notifications" icon={<Bell />} href="/docs/guides/notifications">
    Get alerted when backups fail or complete
  </Card>

  <Card title="Optimize Retention" icon={<History />}>
    Fine-tune retention policies to balance storage and history
  </Card>
</Cards>

## Common Tasks

### Restoring Data

To restore data from a backup:

<Steps>
  <Step>

### Navigate to Backup

Go to **Backups** and select the backup job containing the data you need

</Step>

  <Step>

### Select Snapshot

Choose the snapshot from the time period you want to restore from

</Step>

  <Step>

### Browse and Select

Browse the file tree and select files/folders to restore

</Step>

  <Step>

### Choose Restore Location

Decide whether to restore to original location or specify a new path

</Step>

  <Step>

### Restore

Click **Restore All** to restore everything, or select specific files first and restore only those

</Step>
</Steps>

<Callout type="info">
  For the full restore workflow, overwrite modes, download vs restore, and mounted-volume permission caveats, see [Restoring Data](/docs/guides/restoring).
</Callout>

### Pausing a Backup Job

To temporarily stop a backup from running:

1. Navigate to the backup job
2. Toggle the **Enabled** switch to off
3. The job will skip scheduled runs until re-enabled

### Checking Backup Details

To inspect a backup run:

1. Click on a backup job
2. Review the current **Status**, **Last backup**, and **Snapshots** sections
3. Click a snapshot to inspect its file browser and restore actions
4. For deeper troubleshooting, check `docker compose logs -f zerobyte`

### Deleting Old Snapshots

Backups are automatically pruned according to retention policies, but you can manually delete snapshots:

1. Navigate to backup job
2. Select the snapshot you want to delete
3. Click **Delete Snapshot**
4. Confirm deletion

<Callout type="warn">
  Deleting snapshots is irreversible. Ensure you don't need the data before deleting.
</Callout>

## Troubleshooting

### Backup Failed: Permission Denied

Ensure the volume directory has proper permissions:

```bash
# On the host system
sudo chmod -R 755 /path/to/your/data
```

### Backup Running Slowly

Check:

- Network bandwidth (for cloud repositories)
- Disk I/O performance
- Enable compression if uploading large compressible files
- Review repository upload/download speed limits if you enabled them

### Repository Initialization Failed

Verify:

- Repository path is writable
- For cloud repositories, credentials are correct
- Network connectivity to cloud storage

### Can't Find Mounted Directory

Ensure:

- Directory is mounted in `compose.yaml`
- Container was restarted after mounting: `docker compose restart`
- Path matches the container path, not host path

## Getting Help

If you encounter issues:

<Cards>
  <Card title="Check Logs" icon={<FileText />}>
    View detailed logs with `docker compose logs -f zerobyte`
  </Card>

  <Card title="GitHub Issues" icon={<GithubLogoIcon />}>
    Report bugs or request features at [github.com/nicotsx/zerobyte/issues](https://github.com/nicotsx/zerobyte/issues)
  </Card>

  <Card title="Documentation" icon={<BookOpen />}>
    Explore the full documentation for advanced topics
  </Card>

  <Card title="Community" icon={<Users />}>
    Join discussions and get help from other users
  </Card>
</Cards>

<Callout type="info">
  When reporting issues, include:

  - Zerobyte version (visible in UI footer)
  - Docker and Docker Compose versions
  - Relevant logs from `docker compose logs zerobyte`
  - Steps to reproduce the issue
</Callout>

## Conclusion

You've successfully:

<ul className="my-4 list-none space-y-2 pl-0">
  <li className="flex items-start gap-2">
    <Check className="mt-1.5 h-4 w-4 shrink-0 text-green-600" />
    <span>Created an admin account and downloaded the recovery key</span>
  </li>
  <li className="flex items-start gap-2">
    <Check className="mt-1.5 h-4 w-4 shrink-0 text-green-600" />
    <span>Added a volume to backup</span>
  </li>
  <li className="flex items-start gap-2">
    <Check className="mt-1.5 h-4 w-4 shrink-0 text-green-600" />
    <span>Created a repository for encrypted storage</span>
  </li>
  <li className="flex items-start gap-2">
    <Check className="mt-1.5 h-4 w-4 shrink-0 text-green-600" />
    <span>Configured and ran your first backup job</span>
  </li>
  <li className="flex items-start gap-2">
    <Check className="mt-1.5 h-4 w-4 shrink-0 text-green-600" />
    <span>Verified your backup was successful</span>
  </li>
</ul>

Your data is now protected with encrypted, deduplicated backups. Zerobyte will continue to run backups automatically according to your schedule.

import { GithubLogoIcon } from "@phosphor-icons/react";
import { FolderPlus, Cloud, Bell, History, FileText, BookOpen, Users, Check } from "lucide-react";
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
import { Accordion, Accordions } from "fumadocs-ui/components/accordion";

---

### Apps Docs .Cta.Json (`apps/docs/.cta.json`)

{
	"projectName": "docs",
	"mode": "file-router",
	"typescript": true,
	"packageManager": "npm",
	"includeExamples": false,
	"tailwind": true,
	"addOnOptions": {},
	"envVarValues": {},
	"git": false,
	"routerOnly": false,
	"version": 1,
	"framework": "react",
	"chosenAddOns": ["nitro"]
}

---

### Apps Docs Components.Json (`apps/docs/components.json`)

{
	"$schema": "https://ui.shadcn.com/schema.json",
	"style": "base-lyra",
	"rsc": false,
	"tsx": true,
	"tailwind": {
		"config": "",
		"css": "src/styles.css",
		"baseColor": "neutral",
		"cssVariables": true,
		"prefix": ""
	},
	"iconLibrary": "phosphor",
	"rtl": false,
	"aliases": {
		"components": "#/components",
		"utils": "#/lib/utils",
		"ui": "#/components/ui",
		"lib": "#/lib",
		"hooks": "#/hooks"
	},
	"menuColor": "default",
	"menuAccent": "subtle",
	"registries": {}
}

---

### Apps Docs Package.Json (`apps/docs/package.json`)

{
	"name": "docs",
	"private": true,
	"type": "module",
	"imports": {
		"#/*": "./src/*"
	},
	"scripts": {
		"dev": "vite dev",
		"build": "vite build",
		"preview": "vite preview",
		"deploy": "bun run build && wrangler deploy",
		"cf-typegen": "wrangler types",
		"tsc": "tsc --noEmit"
	},
	"dependencies": {
		"@base-ui/react": "^1.9.0",
		"@fontsource-variable/jetbrains-mono": "^5.3.0",
		"@phosphor-icons/react": "^2.1.10",
		"@tailwindcss/vite": "^4.3.3",
		"@tanstack/react-devtools": "^0.10.13",
		"@tanstack/react-router": "^1.170.45",
		"@tanstack/react-router-devtools": "^1.167.2",
		"@tanstack/react-router-ssr-query": "^1.167.3",
		"@tanstack/react-start": "^1.169.3",
		"@tanstack/router-plugin": "^1.168.47",
		"@types/mdx": "^2.0.14",
		"class-variance-authority": "^0.7.1",
		"clsx": "^2.1.1",
		"fumadocs-core": "^16.17.5",
		"fumadocs-mdx": "^15.4.7",
		"fumadocs-ui": "^16.17.5",
		"lucide-react": "^1.59.0",
		"react": "^19.3.1",
		"react-dom": "^19.3.1",
		"shadcn": "^4.22.0",
		"tailwind-merge": "^3.7.0",
		"tailwindcss": "^4.3.3",
		"tw-animate-css": "^1.4.0"
	},
	"devDependencies": {
		"@cloudflare/vite-plugin": "^1.64.0",
		"@tailwindcss/typography": "^0.5.20",
		"@tanstack/devtools-vite": "^0.8.5",
		"@testing-library/dom": "^10.4.2",
		"@testing-library/react": "^16.3.3",
		"@types/node": "^26.6.5",
		"@types/react": "^19.3.0",
		"@types/react-dom": "^19.3.0",
		"@vitejs/plugin-react": "^6.1.2",
		"jsdom": "^30.1.3",
		"typescript": "^7.0.2",
		"vite": "^8.3.4",
		"vite-plugin-killer-instincts": "^1.1.0",
		"vitest": "^5.0.5",
		"wrangler": "^4.151.1"
	},
	"pnpm": {
		"onlyBuiltDependencies": [
			"esbuild",
			"lightningcss"
		]
	}
}

---

### Apps Docs Tsconfig.Json (`apps/docs/tsconfig.json`)

{
	"include": ["**/*.ts", "**/*.tsx"],
	"compilerOptions": {
		"target": "ES2022",
		"jsx": "react-jsx",
		"module": "ESNext",
		"paths": {
			"#/*": ["./src/*"],
			"@/*": ["./src/*"],
			"fumadocs-mdx:collections/*": ["./.source/*"]
		},
		"lib": ["ES2022", "DOM", "DOM.Iterable"],
		"types": ["vite/client"],

		/* Bundler mode */
		"moduleResolution": "bundler",
		"allowImportingTsExtensions": true,
		"verbatimModuleSyntax": true,
		"noEmit": true,

		/* Linting */
		"skipLibCheck": true,
		"strict": true,
		"noUnusedLocals": true,
		"noUnusedParameters": true,
		"noFallthroughCasesInSwitch": true,
		"noUncheckedSideEffectImports": true
	}
}

---

### Apps Docs .Vscode Settings.Json (`apps/docs/.vscode/settings.json`)

{
	"files.watcherExclude": {
		"**/routeTree.gen.ts": true
	},
	"search.exclude": {
		"**/routeTree.gen.ts": true
	},
	"files.readonlyInclude": {
		"**/routeTree.gen.ts": true
	}
}

---

### Apps Docs Public Manifest.Json (`apps/docs/public/manifest.json`)

{
	"short_name": "TanStack App",
	"name": "Create TanStack App Sample",
	"icons": [
		{
			"src": "favicon.ico",
			"sizes": "64x64 32x32 24x24 16x16",
			"type": "image/x-icon"
		},
		{
			"src": "logo192.png",
			"type": "image/png",
			"sizes": "192x192"
		},
		{
			"src": "logo512.png",
			"type": "image/png",
			"sizes": "512x512"
		}
	],
	"start_url": ".",
	"display": "standalone",
	"theme_color": "#000000",
	"background_color": "#ffffff"
}

---

### Apps Docs Public Robots (`apps/docs/public/robots.txt`)

# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
Sitemap: https://zerobyte.app/sitemap.xml

---

### Apps Docs Content Docs Cli (`apps/docs/content/docs/cli.mdx`)

---
title: CLI Reference
description: Short reference for Zerobyte admin CLI commands
---

The Zerobyte CLI is useful for account recovery and admin maintenance tasks that are easier to run from the server than from the web UI.

Run commands inside the Zerobyte container:

```bash
docker exec -it zerobyte bun run cli <command>
```

Use `--help` to see all options:

```bash
docker exec -it zerobyte bun run cli --help
docker exec -it zerobyte bun run cli reset-password --help
```

Most commands prompt for missing values when run interactively.

## Password Reset

New passwords must be between 8 and 128 characters long. Invalid passwords are rejected without changing the existing password or invalidating sessions.

A successful reset invalidates all existing sessions for that user.

## Commands

| Command               | Useful for                                                                                                          |
| --------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `reset-password`      | Reset a user's password when they are locked out or forgot it. Invalidates existing sessions.                       |
| `disable-2fa`         | Disable two-factor authentication for a user who lost access to their 2FA device.                                   |
| `change-username`     | Rename a user account. Invalidates existing sessions.                                                               |
| `change-email`        | Change a user's email address. Removes linked SSO accounts for that user and invalidates sessions.                  |
| `rekey-2fa`           | Re-encrypt existing 2FA secrets using the current `APP_SECRET`, usually after migrating from an older secret setup. |
| `assign-organization` | Move a user to another organization by organization slug. Invalidates existing sessions.                            |

## Examples

```bash
docker exec -it zerobyte bun run cli reset-password --username admin
docker exec -it zerobyte bun run cli disable-2fa --username admin
docker exec -it zerobyte bun run cli change-email --username admin --email admin@example.com
docker exec -it zerobyte bun run cli assign-organization --username admin --organization default
```

For non-interactive use, pass the command options directly instead of relying on prompts.

---

### Apps Docs Content Docs Configuration (`apps/docs/content/docs/configuration.mdx`)

---
title: Configuration
description: Environment variables, Docker settings, and configuration reference
---

Zerobyte is configured through environment variables and Docker Compose settings. This page covers all available options.

## Environment Variables

### Required

| Variable          | Description                                                                                                                                                           | Example                                                   |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `BASE_URL`        | The URL where Zerobyte will be accessed. Controls cookie security and CORS behavior.                                                                                  | `http://localhost:4096` or `https://zerobyte.example.com` |
| `APP_SECRET`      | Random secret key (32+ characters) used to encrypt sensitive data in the database. Generate with `openssl rand -hex 32`. Set this or `APP_SECRET_FILE`, but not both. | `94bad46e...c66e25d5c2b`                                  |
| `APP_SECRET_FILE` | Alternative to `APP_SECRET`. Path to a file containing the app secret, useful with Docker or Kubernetes secrets. Set this or `APP_SECRET`, but not both.              | `/run/secrets/app_secret`                                 |

<Callout type="warn">
	Never share or commit your `APP_SECRET`. If you lose it, encrypted data (credentials stored for volumes and
	repositories) cannot be recovered.
</Callout>

<Callout type="info">
	Zerobyte reads the contents of `APP_SECRET_FILE`, trims surrounding whitespace and newlines, and applies the same
	32-256 character requirement as `APP_SECRET`.
</Callout>

### Recommended

| Variable | Description                                                               | Default |
| -------- | ------------------------------------------------------------------------- | ------- |
| `TZ`     | Timezone for the container. **Important for accurate backup scheduling.** | `UTC`   |

### Optional

| Variable                  | Description                                                                                                                                                       | Default                |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
| `PORT`                    | Port the web interface and API listen on inside the container.                                                                                                    | `4096`                 |
| `RESTIC_HOSTNAME`         | Hostname used by Restic when creating snapshots. Automatically detected if a custom hostname is set in Docker.                                                    | `zerobyte`             |
| `GOMAXPROCS`              | Optional positive integer passed to Restic processes to limit CPU scheduler threads. See [Performance tuning](/docs/guides/performance-tuning).                   | Restic default         |
| `TRUST_PROXY`             | Trust forwarded host and protocol headers for authentication. See [Reverse Proxy](/docs/guides/reverse-proxy).                                                    | `false`                |
| `TRUSTED_ORIGINS`         | Comma-separated list of additional trusted origins for CORS.                                                                                                      | (none)                 |
| `WEBHOOK_ALLOWED_ORIGINS` | Comma-separated list of HTTP origins allowed for backup webhooks and outbound HTTP notification destinations.                                                     | (none)                 |
| `WEBHOOK_TIMEOUT`         | Timeout for backup webhook requests in seconds.                                                                                                                   | `60`                   |
| `LOG_LEVEL`               | Logging verbosity: `debug`, `info`, `warn`, `error`.                                                                                                              | `info`                 |
| `SERVER_IDLE_TIMEOUT`     | Server idle timeout in seconds. Set `0` to disable it; Bun supports up to `255` seconds.                                                                          | `60`                   |
| `RCLONE_CONFIG_DIR`       | Path to the rclone config directory inside the container.                                                                                                         | `/root/.config/rclone` |
| `PROVISIONING_PATH`       | Path to a JSON file with operator-managed repositories and volumes to sync at startup.                                                                            | (none)                 |
| `ENABLE_REMOTE_AGENTS`    | Enable remote machine enrollment, connections, sources, and backups on the server. Requires a restart.                                                            | `false`                |
| `ZEROBYTE_AGENT_ROOTS`    | Standalone agent JSON allow-list of filesystem roots. Each entry has `id`, `label`, `path`, and optional `allowBackup` (default `true`).                          | `[]`                   |
| `ZEROBYTE_CONTROLLER_URL` | Standalone agent outbound WebSocket URL, normally `wss://host/api/v1/agents/connect`. The public endpoint is enabled in production Bun builds, not `bun run dev`. | (none)                 |
| `ZEROBYTE_AGENT_TOKEN`    | Standalone agent machine credential. Enrollment exchanges its one-time code for this credential; do not set this to the enrollment code.                          | (none)                 |

### Trusted agent filesystem roots

Standalone agents expose local files through an explicit allow-list. Enrollment saves the credential and roots; use the [Remote agents](/docs/guides/remote-agents) guide for that flow. When configuring a saved machine credential through environment variables, set all three agent variables. For example:

```bash
ZEROBYTE_AGENT_ROOTS='[{"id":"data","label":"Application data","path":"/srv/data"}]'
```

IDs and labels must be unique and non-empty. Paths are expanded and canonicalized by the agent at startup; invalid or missing paths prevent startup. Only the ID, label, and backup capability are advertised to the controller. Absolute host paths are never sent. Remote restore is not supported. Enrolled CLI agents load their saved roots when this variable is absent. When both `ZEROBYTE_AGENT_TOKEN` and `ZEROBYTE_CONTROLLER_URL` are set, the agent uses environment configuration instead; set `ZEROBYTE_AGENT_ROOTS` as well or it advertises no filesystem roots. The supervised built-in local agent always exposes `local-filesystem`, which points to its filesystem root (`/` on Linux and macOS). Additional configured roots are added alongside it and cannot replace it. Local and remote agents use the same root ID and relative-path protocol; managed local volumes use `local-filesystem` too.

## Using APP_SECRET_FILE

If you prefer not to place the app secret directly in `environment:`, mount it as a file and point `APP_SECRET_FILE` at that path.

```yaml compose.yaml
services:
  zerobyte:
    environment:
      - BASE_URL=https://zerobyte.example.com
      - APP_SECRET_FILE=/run/secrets/app_secret
    secrets:
      - app_secret

secrets:
  app_secret:
    file: ./secrets/app_secret.txt
```

Generate the secret file with:

```bash
mkdir -p ./secrets
openssl rand -hex 32 > ./secrets/app_secret.txt
```

Do not set `APP_SECRET` at the same time. Zerobyte will fail to start if both are configured.

## Docker Compose Settings

### Volume Mounts

Essential volume mounts for Zerobyte:

```yaml
volumes:
  # Sync container time with host (recommended)
  - /etc/localtime:/etc/localtime:ro

  # Zerobyte data directory (database, encryption keys, local repositories)
  - /var/lib/zerobyte:/var/lib/zerobyte
```

<Callout type="warn">
	**Do not** point `/var/lib/zerobyte` to a network share. This causes permission issues and severe performance
	degradation. Always use local storage.
</Callout>

<Callout type="info">
  **TrueNAS users**: The `/var/lib` path is ephemeral and resets during system upgrades. Create a dedicated ZFS dataset instead:

```yaml
volumes:
  - /mnt/tank/docker/zerobyte:/var/lib/zerobyte
```

</Callout>

#### Additional Volume Mounts

| Mount                                              | Purpose                                                                      |
| -------------------------------------------------- | ---------------------------------------------------------------------------- |
| `/path/to/data:/data:ro`                           | Mount host directories for local directory backups (use `:ro` for read-only) |
| `~/.config/rclone:/root/.config/rclone:ro`         | Mount rclone configuration for rclone-based repositories and volumes         |
| `~/.ssh:/root/.ssh:ro`                             | Mount SSH keys for rclone SFTP remotes that use `key_file`                   |
| `./provisioning.json:/config/provisioning.json:ro` | Mount a provisioning file for operator-managed resources                     |

### Container Capabilities

Zerobyte supports two deployment modes depending on your needs:

#### Full Installation (with remote mounts)

Required for mounting NFS, SMB, WebDAV, and SFTP volumes directly from Zerobyte:

```yaml
cap_add:
  - SYS_ADMIN
devices:
  - /dev/fuse:/dev/fuse
```

#### Simplified Installation (local directories only)

If you only need local directory backups, no special capabilities are required:

```yaml
# No cap_add or devices needed
ports:
  - "4096:4096"
```

### Port Configuration

By default, Zerobyte listens on port 4096:

```yaml
ports:
  - "4096:4096"
```

To bind to localhost only (recommended when using a reverse proxy):

```yaml
ports:
  - "127.0.0.1:4096:4096"
```

## Cookie Security

The `BASE_URL` determines how authentication cookies behave:

| BASE_URL                       | Cookie Behavior                                       |
| ------------------------------ | ----------------------------------------------------- |
| `http://192.168.1.50:4096`     | Secure cookies **disabled**, allows login over HTTP   |
| `http://localhost:4096`        | Secure cookies **disabled**, allows local development |
| `https://zerobyte.example.com` | Secure cookies **enabled**, requires HTTPS            |

<Callout type="info">
	If `BASE_URL` starts with `https://`, browsers will only send auth cookies over HTTPS connections. Plain HTTP access
	may show the login page but authentication will fail.
</Callout>

<Callout type="warn">
	`TRUSTED_ORIGINS` only allows additional origins for CORS. It does **not** disable secure cookies or make HTTP access
	work when `BASE_URL` is HTTPS.
</Callout>

## Secret References

When provisioning volumes or repositories, sensitive fields support secret references:

| Reference             | Resolves From                               | Example               |
| --------------------- | ------------------------------------------- | --------------------- |
| `env://VARIABLE_NAME` | Container environment variable              | `env://S3_SECRET_KEY` |
| `file://secret_name`  | Docker secret at `/run/secrets/secret_name` | `file://smb_password` |

This allows you to keep credentials in your deployment configuration rather than writing them directly into the provisioning file.

<Callout type="info">
	The standard volume and repository forms in the UI currently store the value you enter, encrypted at rest. `env://`
	and `file://` references are only resolved during provisioning.
</Callout>

### Example with Docker Secrets

```yaml compose.yaml
services:
  zerobyte:
    environment:
      - S3_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
    secrets:
      - s3_secret_key
    volumes:
      - /var/lib/zerobyte:/var/lib/zerobyte

secrets:
  s3_secret_key:
    file: ./secrets/s3_secret_key.txt
```

In the provisioning file, reference these as:

- Access Key: `env://S3_ACCESS_KEY`
- Secret Key: `file://s3_secret_key`

## Updating Zerobyte

To update to a new version:

```bash
docker compose pull
docker compose up -d
docker compose logs -f zerobyte
```

<Callout type="warn">
	Always check the [release notes](https://github.com/nicotsx/zerobyte/releases) before updating, especially for v0.x.x
	versions which may include breaking changes.
</Callout>

import { Step, Steps } from "fumadocs-ui/components/steps";

---

### Apps Docs Content Docs Installation (`apps/docs/content/docs/installation.mdx`)

---
title: Installation
description: Deploy Zerobyte with Docker and Docker Compose
---

import { Check, LifeBuoy, Rocket, Settings, X } from "lucide-react";
import { Step, Steps } from "fumadocs-ui/components/steps";

Zerobyte runs as a Docker container and requires Docker and Docker Compose to be installed on your server.

## Prerequisites

<Steps>
<Step>

### Install Docker

Ensure Docker is installed on your server. Visit [docs.docker.com](https://docs.docker.com/get-docker/) for installation instructions for your platform.

</Step>

<Step>

### Install Docker Compose

Docker Compose is required for orchestration. It's included with Docker Desktop or can be installed separately on Linux servers.

</Step>

<Step>

### Check Installation

Verify your installation by running:

```bash
docker --version
docker compose version
```

</Step>
</Steps>

## Basic Installation

The standard installation includes remote mount support (NFS, SMB, WebDAV, SFTP) and requires elevated container capabilities.

### 1. Create compose.yaml

Create a `compose.yaml` file with the following configuration:

```yaml compose.yaml
services:
  zerobyte:
    image: ghcr.io/nicotsx/zerobyte:v0.43
    container_name: zerobyte
    restart: unless-stopped
    cap_add:
      - SYS_ADMIN
    ports:
      - "4096:4096"
    devices:
      - /dev/fuse:/dev/fuse
    environment:
      - TZ=Europe/Zurich # Set your timezone here
      - BASE_URL=http://localhost:4096 # URL you will use to access Zerobyte
      - APP_SECRET=94bad46...c66e25d5c2b # Generate your own secret with `openssl rand -hex 32`
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/lib/zerobyte:/var/lib/zerobyte
```

<Callout type="warn">
**Security Note**: The `SYS_ADMIN` capability and `/dev/fuse` device are required for mounting remote filesystems (NFS, SMB, WebDAV, SFTP). If you only need local directory backups, see the [Simplified Installation](#simplified-installation-no-remote-mounts) section below.

Remote mounts are convenient, but they can expose translated ownership, permissions, and ACL metadata instead of the source system's original view. Read [Mounted Shares and Permissions](/docs/guides/mounted-shares-and-acls) before using mounted volumes for metadata-sensitive backups.
</Callout>

### 2. Configure Environment Variables

Update the environment variables in your `compose.yaml`:

**Generate APP_SECRET:**

```bash
openssl rand -hex 32
```

**Example Configuration:**

```yaml
environment:
  - TZ=America/New_York
  - BASE_URL=http://192.168.1.100:4096
  - APP_SECRET=a1b2c3d4e5f6...  # Output from openssl command
```

#### Required Environment Variables

| Variable | Description | Example |
|----------|-------------|---------|
| `BASE_URL` | **Required.** The base URL where Zerobyte will be accessed. Used for cookie security and CORS. | `http://localhost:4096` or `https://zerobyte.example.com` |
| `APP_SECRET` | **Required.** A 32+ character random secret for encrypting sensitive data in the database. Generate with `openssl rand -hex 32`. | `94bad46...c66e25d5c2b` |
| `TZ` | **Recommended.** Timezone for accurate backup scheduling. | `Europe/Zurich`, `America/New_York`, `UTC` |

<Callout type="info">
The `BASE_URL` determines cookie security behavior:

* **HTTP or IP addresses**: Secure cookies disabled (allows local access)
* **HTTPS with domain**: Secure cookies enabled (required for production)
</Callout>

#### Optional Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `PORT` | Port the web interface listens on inside the container | `4096` |
| `RESTIC_HOSTNAME` | Hostname used by Restic in snapshots | `zerobyte` |
| `TRUSTED_ORIGINS` | Comma-separated list of additional trusted CORS origins | (none) |
| `WEBHOOK_TIMEOUT` | Timeout for backup webhook requests in seconds | `60` |
| `LOG_LEVEL` | Logging verbosity: `debug`, `info`, `warn`, `error` | `info` |
| `SERVER_IDLE_TIMEOUT` | Server idle timeout in seconds. Set `0` to disable it; Bun supports up to `255` seconds. | `60` |
| `RCLONE_CONFIG_DIR` | Path to rclone config directory inside container | `/root/.config/rclone` |

### 3. Configure Volume Mounts

The essential volume mount stores Zerobyte's data:

```yaml
volumes:
  - /etc/localtime:/etc/localtime:ro
  - /var/lib/zerobyte:/var/lib/zerobyte
```

<Callout type="warn">
**Important**: Do not point `/var/lib/zerobyte` to a network share. This will cause permission issues and severe performance degradation. Always use local storage.
</Callout>

<Callout type="info">
**TrueNAS Users**: The `/var/lib` path is ephemeral on TrueNAS and resets during system upgrades. Instead, create a dedicated ZFS dataset:

```yaml
volumes:
  - /etc/localtime:/etc/localtime:ro
  - /mnt/tank/docker/zerobyte:/var/lib/zerobyte
```

This ensures your configuration, encryption keys, and database persist across upgrades.
</Callout>

### 4. Start Zerobyte

Start the container using Docker Compose:

```bash
docker compose up -d
```

Verify the container is running:

```bash
docker compose ps
docker compose logs -f zerobyte
```

### 5. Access the Web Interface

Once the container is running, access Zerobyte at the URL you specified in `BASE_URL`:

```
http://<your-server-ip>:4096
```

<Callout type="info">
On first access, you'll be prompted to create an admin account. This account will have full access to all backup management features.
</Callout>

## Simplified Installation (No Remote Mounts)

If you only need to back up locally mounted directories and don't require remote share mounting (NFS, SMB, WebDAV, SFTP), you can use a reduced-privilege deployment:

```yaml compose.yaml
services:
  zerobyte:
    image: ghcr.io/nicotsx/zerobyte:v0.43
    container_name: zerobyte
    restart: unless-stopped
    ports:
      - "4096:4096"
    environment:
      - TZ=Europe/Zurich
      - BASE_URL=http://localhost:4096
      - APP_SECRET=94bad46...c66e25d5c2b
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/lib/zerobyte:/var/lib/zerobyte
      - /path/to/your/directory:/mydata
```

**Trade-offs:**

<ul className="my-4 list-none space-y-2 pl-0">
  <li className="flex items-start gap-2">
    <Check className="mt-1.5 h-4 w-4 shrink-0 text-green-600" />
    <span>Improved security by removing `SYS_ADMIN` capability</span>
  </li>
  <li className="flex items-start gap-2">
    <Check className="mt-1.5 h-4 w-4 shrink-0 text-green-600" />
    <span>Support for local directories mounted into the container</span>
  </li>
  <li className="flex items-start gap-2">
    <Check className="mt-1.5 h-4 w-4 shrink-0 text-green-600" />
    <span>All repository types still supported (local, S3, GCS, Azure, rclone)</span>
  </li>
  <li className="flex items-start gap-2">
    <X className="mt-1.5 h-4 w-4 shrink-0 text-red-600" />
    <span>Cannot mount remote shares (NFS, SMB, WebDAV, SFTP) from within Zerobyte</span>
  </li>
</ul>

<Callout type="info">
If you need remote mount capabilities later, you can update your `compose.yaml` to add back the `cap_add: SYS_ADMIN` and `devices: /dev/fuse:/dev/fuse` directives.

If your goal is the closest thing to true replication, prefer local bind-mounted directories whenever possible. See [Mounted Shares and Permissions](/docs/guides/mounted-shares-and-acls) for the practical tradeoffs.
</Callout>

## Mounting Local Directories

To back up directories from your host system, mount them into the container:

```yaml
volumes:
  - /etc/localtime:/etc/localtime:ro
  - /var/lib/zerobyte:/var/lib/zerobyte
  - /path/to/your/photos:/photos
  - /path/to/your/documents:/documents
  - /path/to/your/media:/media
```

<Callout type="info">
Use read-only mounts (`:ro`) if you want to prevent Zerobyte from modifying the source data. But you won't be able to restore files back to the original location if you use read-only mounts.
</Callout>

After adding volume mounts, restart the container:

```bash
docker compose down
docker compose up -d
```

The mounted directories will be available inside the container at the specified paths (e.g., `/photos`, `/documents`, `/media`).

## Advanced Configuration

### Using Docker Secrets for Sensitive Data

If you use provisioning, Zerobyte can resolve secrets from environment variables or Docker secret files before storing the resolved value encrypted in the database:

```yaml compose.yaml
services:
  zerobyte:
    image: ghcr.io/nicotsx/zerobyte:v0.43
    container_name: zerobyte
    restart: unless-stopped
    cap_add:
      - SYS_ADMIN
    devices:
      - /dev/fuse:/dev/fuse
    ports:
      - "4096:4096"
    environment:
      - TZ=Europe/Zurich
      - BASE_URL=http://localhost:4096
      - APP_SECRET=94bad46...c66e25d5c2b
      - S3_ACCESS_KEY=your-access-key
      - S3_SECRET_KEY=your-secret-key
    secrets:
      - smb_password
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/lib/zerobyte:/var/lib/zerobyte

secrets:
  smb_password:
    file: ./secrets/smb_password.txt
```

When authoring a provisioning file, you can reference these secrets:

* `env://S3_SECRET_KEY` - Resolves from environment variable `S3_SECRET_KEY`
* `file://smb_password` - Resolves from `/run/secrets/smb_password`

<Callout type="info">
  These references are currently resolved only during provisioning. The normal volume and repository forms in the UI expect the actual secret value. See the [Provisioning guide](/docs/guides/provisioning) for the full workflow.
</Callout>

### Mounting rclone Configuration

To use rclone-based repositories (Google Drive, Dropbox, OneDrive, etc.), mount your rclone configuration:

<Steps>
<Step>

### Configure rclone on Host

Install and configure rclone on your host system:

```bash
curl https://rclone.org/install.sh | sudo bash
rclone config
```

</Step>

<Step>

### Mount Config into Container

Update your `compose.yaml`:

```yaml
volumes:
  - /etc/localtime:/etc/localtime:ro
  - /var/lib/zerobyte:/var/lib/zerobyte
  - ~/.config/rclone:/root/.config/rclone:ro
```

</Step>

<Step>

### Restart Container

```bash
docker compose down && docker compose up -d
```

</Step>
</Steps>

Your rclone remotes will now be available when creating repositories in Zerobyte.

### Reverse Proxy Setup

If you're running Zerobyte behind a reverse proxy (Nginx, Caddy, Traefik), see the [Reverse Proxy guide](/docs/guides/reverse-proxy) for full configuration examples.

## Next Steps

Now that Zerobyte is installed, proceed to the Quick Start guide to configure your first backup:

<Cards>
<Card title="Quick Start Guide" icon={<Rocket />} href="/docs/quickstart">
  Set up your first volume, repository, and backup job
</Card>

<Card title="Configuration Reference" icon={<Settings />} href="/docs/configuration">
  All environment variables and Docker settings
</Card>

<Card title="Troubleshooting" icon={<LifeBuoy />} href="/docs/troubleshooting">
  Solve common issues with permissions, mounts, and more
</Card>
</Cards>

---

### Apps Docs Content Docs Introduction (`apps/docs/content/docs/introduction.mdx`)

---
title: Introduction
description: Learn about Zerobyte, a powerful backup automation platform built on Restic
---

Zerobyte is a backup automation tool that helps you protect your data across multiple storage backends. Built on top of [Restic](https://restic.net/), it provides a modern web interface to schedule, manage, and monitor encrypted backups.

<Callout type="warn">
  Zerobyte is still in version 0.x.x and is subject to major changes between versions. Core features are under active development
</Callout>

## Key Features

<Cards>
  <Card title="Automated Backups" icon={<Clock />}>
    Schedule encrypted, compressed backups with fine-grained retention policies powered by Restic
  </Card>

  <Card title="Flexible Scheduling" icon={<Calendar />}>
    Configure automated backup jobs with cron expressions and sophisticated retention rules
  </Card>

  <Card title="End-to-End Encryption" icon={<Lock />}>
    Your data is always protected with strong encryption at rest, ensuring security for every snapshot
  </Card>

  <Card title="Multi-Protocol Support" icon={<Network />}>
    Back up from NFS, SMB, WebDAV, SFTP, or local directories with ease
  </Card>
</Cards>

## Supported Storage

### Volume Sources (What to Back Up)

Volumes represent the data you want to protect:

- **Local directories**, directories mounted into the container
- **NFS**, Network File System shares (v3, v4, v4.1)
- **SMB/CIFS**, Windows and Samba shares
- **WebDAV**, WebDAV-compatible storage (Nextcloud, ownCloud, etc.)
- **SFTP**, SSH File Transfer Protocol servers
- **Rclone**, 40+ cloud storage providers via rclone (Google Drive, Dropbox, OneDrive, etc.)

### Repository Destinations (Where to Store Backups)

Repositories are encrypted storage locations for your backup snapshots:

- **Local directories**, store backups on local disk
- **S3-compatible storage**, Amazon S3, MinIO, Wasabi, Backblaze B2, DigitalOcean Spaces, and more
- **Cloudflare R2**, S3-compatible with zero egress fees
- **Google Cloud Storage**, Google's cloud storage service
- **Azure Blob Storage**, Microsoft Azure storage
- **SFTP**, remote servers accessible via SSH
- **REST server**, Restic REST server protocol
- **Rclone remotes**, 40+ cloud storage providers via rclone

## How It Works

Zerobyte operates on three core concepts:

<Steps>
  <Step>

### Volumes

Define **volumes** as the source data you want to back up. These can be local directories, network shares, or cloud storage mounted via various protocols. Zerobyte handles mounting, health monitoring, and auto-recovery automatically.

  </Step>

  <Step>

### Repositories

Create **repositories** as encrypted storage destinations. Repositories use Restic's deduplication and encryption to store your backups efficiently and securely. Choose from local disks, cloud object storage, or any of 40+ rclone backends.

  </Step>

  <Step>

### Backup Jobs

Configure **backup jobs** that connect volumes to repositories with scheduling rules and retention policies. Jobs run automatically according to your schedule, with real-time progress monitoring in the web interface.

  </Step>
</Steps>

<Callout type="info">
  All backups are encrypted using Restic's encryption. Your encryption password is securely stored and never leaves your server.
</Callout>

## Use Cases

<Accordions>
  <Accordion title="Home Server Backups">
    Back up your home server data (photos, documents, media) to cloud storage or an external drive with automatic scheduling and retention.
  </Accordion>

  <Accordion title="NAS Data Protection">
    Protect your NAS data by backing up critical shares to offsite cloud storage with encryption and versioning.
  </Accordion>

  <Accordion title="Development Server Backups">
    Automatically back up development servers, databases, and configuration files to ensure quick recovery.
  </Accordion>

  <Accordion title="Multi-Site Backups">
    Centralize backup management for multiple locations, backing up various sources to different repository types.
  </Accordion>
</Accordions>

## Why Restic?

Zerobyte is built on [Restic](https://restic.net/) because it offers:

- **Fast and efficient**, incremental backups with content-defined chunking
- **Secure by design**, strong encryption and authenticated snapshots
- **Deduplication**, only unique data is stored, saving space
- **Verification**, built-in integrity checking for peace of mind
- **Cross-platform**, works across different operating systems
- **Well-maintained**, an active open-source project with regular updates

## Next Steps

<Cards>
  <Card title="Installation" icon={<Download />} href="/docs/installation">
    Deploy Zerobyte with Docker and Docker Compose
  </Card>

  <Card title="Quick Start" icon={<Rocket />} href="/docs/quickstart">
    Set up your first backup in minutes
  </Card>
</Cards>

import { Clock, Calendar, Lock, Network, Download, Rocket } from "lucide-react";
import { Step, Steps } from "fumadocs-ui/components/steps";
import { Accordion, Accordions } from "fumadocs-ui/components/accordion";

---

### Apps Docs Content Docs Meta.Json (`apps/docs/content/docs/meta.json`)

{
	"title": "Documentation",
	"pages": [
		"introduction",
		"installation",
		"quickstart",
		"---Concepts---",
		"...concepts",
		"---Guides---",
		"...guides",
		"---Reference---",
		"configuration",
		"cli",
		"troubleshooting"
	]
}

---

### Apps Docs Content Docs Troubleshooting (`apps/docs/content/docs/troubleshooting.mdx`)

---
title: Troubleshooting
description: Solve common issues with permissions, mounts, rclone, and more
---

This guide covers common issues and their solutions. Before troubleshooting, enable debug logging to get more detail.

## Enable Debug Logging

Add `LOG_LEVEL=debug` to your environment variables and restart:

```yaml
environment:
  - LOG_LEVEL=debug
```

Then view logs:

```bash
docker logs -f zerobyte
```

<Callout type="warn">
  Never share sensitive information (passwords, access keys, personal data) in public issue reports. Redact them from logs before posting.
</Callout>

## Container Won't Start

1. Check logs: `docker compose logs zerobyte`
2. Verify exactly one of `APP_SECRET` or `APP_SECRET_FILE` is set
3. If using `APP_SECRET_FILE`, ensure the file exists inside the container, is readable, and contains a 32-256 character secret
4. Ensure `/var/lib/zerobyte` exists and has correct permissions
5. Verify port 4096 is not already in use: `netstat -tuln | grep 4096`

### Permission Issues

If you encounter permission errors on the data directory:

```bash
sudo chown -R 1000:1000 /var/lib/zerobyte
```

## Remote Mount Issues

### Permission Denied When Mounting

Mounting remote filesystems requires kernel-level privileges. Ensure:

- Remote share credentials are correct
- The host kernel supports the target filesystem (e.g., CIFS module is available)
- Docker is running in **rootful mode** (rootless Docker cannot perform kernel mounts)
- Your container has `SYS_ADMIN` capability:
  ```yaml
  cap_add:
    - SYS_ADMIN
  ```

### Alternative: Mount on Host

If container-level mounting causes issues, mount the remote share on the host first, then bind-mount it into the container:

```bash
# Mount on host
sudo mount -t cifs //server/share /mnt/your-remote-share -o credentials=/path/to/creds
```

```yaml
# Then in compose.yaml
volumes:
  - /mnt/your-remote-share:/data
```

### Security Framework Issues

Linux security frameworks may block mount operations even with `SYS_ADMIN`. Try these solutions in order:

<Tabs items={["AppArmor (Ubuntu/Debian)", "Seccomp", "SELinux (CentOS/Fedora)"]}>
<Tab value="AppArmor (Ubuntu/Debian)">

Check if AppArmor is blocking mounts:

```bash
sudo aa-status
docker inspect --format='{{.AppArmorProfile}}' zerobyte
```

If it returns `docker-default`, disable AppArmor for the container:

```yaml
security_opt:
  - apparmor:unconfined
```

</Tab>
<Tab value="Seccomp">

Docker's default seccomp profile may block mount-related syscalls:

```yaml
security_opt:
  - seccomp:unconfined
```

</Tab>
<Tab value="SELinux (CentOS/Fedora)">

Adjust the SELinux context:

```yaml
security_opt:
  - label:type:container_runtime_t
```

Or disable SELinux enforcement for the container:

```yaml
security_opt:
  - label:disable
```

</Tab>
</Tabs>

<Callout type="warn">
  **Last resort**: If nothing else works, you can run with `privileged: true`. This disables most container isolation and should only be used temporarily for diagnosis.
</Callout>

## FUSE Mount Failures

FUSE-based mounts (SFTP volumes, rclone volumes) require `/dev/fuse`:

```yaml
devices:
  - /dev/fuse:/dev/fuse
```

Verify FUSE is accessible inside the container:

```bash
docker exec zerobyte ls -la /dev/fuse
```

<Callout type="info">
  `/dev/fuse` is **not required** for SMB/CIFS or NFS mounts, only for SFTP and rclone volumes.
</Callout>

## Rclone Issues

### Test on Host First

Before reporting rclone issues, verify rclone works on your Docker host:

```bash
# List configured remotes
rclone listremotes

# Test listing a remote
rclone lsd myremote:

# Test reading files
rclone ls myremote:path/to/test
```

**If these commands fail on the host, fix your rclone configuration first.** Common causes:
- Expired OAuth tokens, run `rclone config` to re-authenticate
- Incorrect credentials
- Missing permissions on the cloud provider side
- Network or firewall issues

### "No Remotes Available" in Dropdown

Zerobyte can't find your rclone configuration. Check:

```bash
docker exec zerobyte ls -la /root/.config/rclone/
```

Ensure the config is mounted:

```yaml
volumes:
  - ~/.config/rclone:/root/.config/rclone:ro
```

For non-root containers, set the correct path:

```yaml
environment:
  - RCLONE_CONFIG_DIR=/home/appuser/.config/rclone
volumes:
  - ~/.config/rclone:/home/appuser/.config/rclone:ro
```

**Restart the container** after mounting the config.

### "Failed to Create File System" Error

Usually an authentication failure. On your host:
1. Run `rclone config` and re-authenticate the remote
2. Verify with `rclone lsd remote:`
3. Restart the Zerobyte container

### SFTP Repository Authentication Failures

If your rclone SFTP remote uses `key_file`, the path points to the host filesystem which isn't accessible inside the container.

Solutions:
- **Mount SSH keys**: `~/.ssh:/root/.ssh:ro`
- **Embed key in config**: Use `key_pem` instead of `key_file`
- **Use ssh-agent**: Set `key_use_agent = true` and mount the SSH agent socket

See the [rclone guide](/docs/guides/rclone) for detailed instructions.

### Rclone Volume Mount Errors

Rclone volumes (mounting cloud storage as a data source) require:
- Linux host (Windows/macOS cannot use rclone volumes)
- `/dev/fuse` device mounted
- `SYS_ADMIN` capability

**"fusermount3: Permission denied"**: Verify `/dev/fuse` is mounted and check for AppArmor/seccomp restrictions.

**"Operation not permitted"**: Add `SYS_ADMIN` capability.

## Backup Issues

### Backups or restores use too much CPU

Start with the performance tuning guide. The usual fixes are:

- Set `GOMAXPROCS=1` or `GOMAXPROCS=2` on the Zerobyte container and restart
- Change repository compression mode from `max` to `auto` or `off`
- Add `--read-concurrency 1` to the affected backup schedule's custom Restic parameters

See [Performance tuning](/docs/guides/performance-tuning) for the full workflow and supported advanced flags.

### Restore Fails with `lchown` or `operation not permitted`

If you restore directly to SFTP, SMB, WebDAV, rclone, or another mounted filesystem, the destination may accept file contents but reject ownership, mode, timestamp, or xattr updates.

See [Restoring Data](/docs/guides/restoring) for the full restore workflow, the explanation, and the safest workarounds.

### Backup Failed: Permission Denied

Ensure the volume directory has proper permissions:

```bash
sudo chmod -R 755 /path/to/your/data
```

### Backup Running Slowly

- Check network bandwidth (for cloud repositories)
- Check disk I/O performance
- Enable compression for large compressible files
- Try `--read-concurrency 4` or `--read-concurrency 8` for fast local SSD/NVMe sources
- Consider bandwidth limits if other services are affected

See [Performance tuning](/docs/guides/performance-tuning) before changing multiple performance settings at once.

### Repository Locked

If you see "repository is locked" errors, a previous operation may have been interrupted. Use the **Unlock** button in the repository settings.

<Callout type="warn">
  Only unlock if you're certain no other backup or restore operations are running against the repository.
</Callout>

## Getting Help

If you've tried the solutions above and still have issues:

1. Enable debug logging (`LOG_LEVEL=debug`)
2. Collect relevant logs: `docker compose logs --tail=200 zerobyte`
3. Open an issue at [github.com/nicotsx/zerobyte/issues](https://github.com/nicotsx/zerobyte/issues)

Include in your report:
- Zerobyte version (visible in the UI footer)
- Docker and Docker Compose versions
- Relevant logs (with sensitive data redacted)
- Steps to reproduce the issue

import { Tab, Tabs } from "fumadocs-ui/components/tabs";

---

### Apps Docs Content Docs Concepts Backups (`apps/docs/content/docs/concepts/backups.mdx`)

---
title: Backups
description: Learn how backup jobs connect volumes to repositories with scheduling, retention policies, and restore capabilities
---

Learn how backup jobs connect volumes to repositories with scheduling, retention policies, and restore capabilities.

## What are backup jobs?

A backup job is the central piece of Zerobyte. It defines four things:

- **What** to back up, a [volume](/docs/concepts/volumes) (your data source)
- **Where** to store it, a [repository](/docs/concepts/repositories) (your encrypted storage destination)
- **When** to run, a cron schedule
- **How long** to keep snapshots, a retention policy

Each time a backup job runs, Restic creates an incremental, encrypted snapshot of the volume's contents and stores it in the repository.

## Creating a backup job

To create a backup job, navigate to **Backups** in the sidebar and click **Create a backup job**. First choose a volume, then fill in the backup form:

- **Backup name**, a descriptive name for the job (e.g., "Daily Database Backup")
- **Backup repository**, select from your configured repositories
- **Backup frequency**, choose `Manual only`, `Hourly`, `Daily`, `Weekly`, `Specific days`, or `Custom (Cron)`
- **Retention policy**, optionally define how many snapshots to keep

<Callout type="info">
  Choose **Manual only** if you want to create the job without scheduled runs and trigger it yourself.
</Callout>

## Scheduling

Backup jobs use **cron expressions** to define when they run. Cron syntax gives you precise control over scheduling.

### Common cron patterns

| Cron Expression   | Schedule                     |
| ----------------- | ---------------------------- |
| `0 2 * * *`       | Daily at 2:00 AM             |
| `0 2 * * 0`       | Weekly on Sunday at 2:00 AM  |
| `0 2 1 * *`       | Monthly on the 1st at 2:00 AM |
| `0 */6 * * *`     | Every 6 hours                |
| `0 0 * * 1-5`     | Weekdays at midnight         |

<Callout type="info">
  The timezone for cron schedules is controlled by the `TZ` environment variable set on the Zerobyte container. If `TZ` is not set, the system defaults to UTC.
</Callout>

## Retention policies

Retention policies control how long snapshots are kept. After each backup, Restic automatically prunes snapshots that fall outside the defined policy, freeing up storage space.

You can configure the following retention rules:

| Rule           | Description                                |
| -------------- | ------------------------------------------ |
| **Keep Last**    | Number of most recent snapshots to keep    |
| **Keep Daily**   | Number of daily snapshots to keep          |
| **Keep Weekly**  | Number of weekly snapshots to keep         |
| **Keep Monthly** | Number of monthly snapshots to keep        |
| **Keep Yearly**  | Number of yearly snapshots to keep         |

### Example retention policy

A balanced policy for most workloads:

- **Keep Daily**: 7, one snapshot per day for the past week
- **Keep Weekly**: 4, one snapshot per week for the past month
- **Keep Monthly**: 6, one snapshot per month for the past half year
- **Keep Yearly**: 1, one snapshot per year for long-term archival

<Callout type="info">
  Retention rules are additive. A snapshot that satisfies any rule is kept. Restic determines which snapshots best represent each time period automatically.
</Callout>

## Include and exclude paths

By default, the entire volume is backed up. You can refine this with include and exclude patterns.

### Include paths

Specify subdirectories within the volume to back up. Only these paths will be included in the snapshot.

### Exclude patterns

Exclude files or directories that do not need to be backed up. Common patterns:

- `*.tmp`, temporary files
- `node_modules/`, dependency directories
- `.cache/`, cache directories
- `*.log`, log files

<Callout type="warn">
  Exclude patterns use Restic's pattern matching. Test your patterns after the first backup by browsing the snapshot contents to confirm the right files are included.
</Callout>

## Compression

Backups inherit the compression mode from the repository they write to. Compression is configured per repository and applies to all backup jobs targeting that repository.

Available modes are **auto** (default), **off**, and **max**. See [Repositories](/docs/concepts/repositories) for details on each mode.

## Running backups

There are two ways to run a backup job:

- **Scheduled**, the job runs automatically according to its cron schedule. No intervention required.
- **Manual**, click **Backup now** on any backup job to trigger it immediately, regardless of the schedule.

Both methods produce identical snapshots. Manual runs are useful for verifying a new backup job or creating an extra snapshot before a major change.

<Callout type="info">
  You can monitor backup progress in real time through the web interface. Zerobyte streams file counts, data processed, and upload progress as the backup runs.
</Callout>

## Backup webhooks

Backup jobs can run optional HTTP webhooks immediately before and after Restic. Pre-backup webhooks are useful for preparing the source, such as pausing a service or creating a dump. Post-backup webhooks are useful for cleanup, such as resuming a service after the snapshot.

Pre-backup hook failures stop the backup before Restic runs. Post-backup hook failures are recorded with the run result; a clean backup becomes a warning if the post hook fails.

For setup details and a container stop/start example, see [Backup Webhooks](/docs/guides/backup-webhooks).

## Backup status

The UI exposes two related status views:

- In the **Backups** list, the status dot shows **Active**, **Paused**, **Error**, **Warning**, or **Backup in progress**
- In the backup job details page, the last run status shows **Success**, **Error**, **Warning**, or **in progress**
- If a backup job has not completed a run yet, the details page shows no last run value

## Snapshots

Each successful backup creates a snapshot in the repository. Snapshots have several important properties:

- **Immutable**, once created, a snapshot cannot be modified
- **Incremental**, only changed data since the last snapshot is uploaded
- **Browsable**, you can explore snapshot contents directly in the web interface, viewing file sizes, modification times, and directory structure

The first backup uploads all data and may take longer. Subsequent backups are significantly faster because Restic deduplicates data at the chunk level and only transfers new or changed blocks.

## Restoring data

Zerobyte lets you browse snapshots, restore them back to a filesystem path, or download the selected contents directly.

For the complete restore workflow, overwrite modes, custom-target guidance, and mounted-volume permission caveats, see [Restoring Data](/docs/guides/restoring).

At a high level, restoring works like this:

<Steps>
  <Step>

### Navigate to the backup job

Go to **Backups** and select the job containing the data you need.

  </Step>

  <Step>

### Select a snapshot

Choose the snapshot from the time period you want to restore from.

  </Step>

  <Step>

### Browse and select files

Navigate the file tree and select the files or folders you want to restore.

  </Step>

  <Step>

### Choose a restore location

Decide whether to restore to the original location or specify a custom path.

  </Step>

  <Step>

### Restore

Click **Restore** and wait for the operation to complete.

  </Step>
</Steps>

<Callout type="warn">
  By default, restored files are placed back in their original location. This can overwrite current files. If you want to inspect the restored data before replacing anything, specify an alternate restore path.
</Callout>

## Mirror repositories

You can optionally copy snapshots to additional repositories after each backup. Mirrors provide:

- **Geographic redundancy**, store copies in different regions or data centers
- **Provider diversification**, avoid depending on a single cloud provider
- **Compliance**, meet data locality or regulatory requirements

Mirrors are configured per backup job. After each successful backup, snapshots are automatically copied to all enabled mirror repositories.

For a practical pattern that combines fast local restores with an offsite copy, see [3-2-1 Backup Strategy](/docs/guides/3-2-1-backup-strategy).

## Monitoring

The Zerobyte web interface provides visibility into each backup job:

- **Schedule**, the cron expression or `Manual only` configuration for the job
- **Last backup**, when the most recent run happened
- **Next backup**, when the job is expected to run next
- **Snapshot history**, the list of snapshots created for that job
- **Snapshot statistics**, data added, data stored, files processed, bytes processed, and duration for the selected snapshot
- **Restore actions**, restore or download data from the selected snapshot

## Best practices

<Accordions type="multiple">
  <Accordion title="Schedule backups during off-peak hours">
    Run backups when system load is lowest to minimize impact on production workloads. Late night or early morning schedules (e.g., `0 2 * * *`) work well for most setups.
  </Accordion>

  <Accordion title="Use retention policies to balance storage and history">
    Keeping every snapshot indefinitely wastes storage. A policy like 7 daily, 4 weekly, 6 monthly, and 1 yearly provides good coverage without excessive cost.
  </Accordion>

  <Accordion title="Test restores regularly">
    A backup is only valuable if you can restore from it. Periodically restore files to a test location to verify data integrity and familiarize yourself with the restore process.
  </Accordion>

  <Accordion title="Use exclude patterns for temporary files">
    Excluding directories like `node_modules/`, `.cache/`, and `*.tmp` files reduces backup size and duration without losing important data.
  </Accordion>

  <Accordion title="Monitor failed backups and set up notifications">
    Check the backup dashboard regularly for failed jobs. Configure notifications so you are alerted immediately when a backup fails rather than discovering it during a restore. See the [Notifications guide](/docs/guides/notifications) for destination setup, event routing, and delivery examples.
  </Accordion>
</Accordions>

## Next steps

<Cards>
  <Card title="Configuration" icon={<Settings />} href="/docs/configuration">
    Configure environment variables, Docker secrets, and deployment options
  </Card>

  <Card title="Troubleshooting" icon={<LifeBuoy />} href="/docs/troubleshooting">
    Diagnose and resolve common backup issues
  </Card>
</Cards>

import { Step, Steps } from "fumadocs-ui/components/steps";
import { Accordion, Accordions } from "fumadocs-ui/components/accordion";
import { Card, Cards } from "../../../src/components/DocsCard";
import { Callout } from "fumadocs-ui/components/callout";
import { Settings, LifeBuoy } from "lucide-react";

---

### Apps Docs Content Docs Concepts Meta.Json (`apps/docs/content/docs/concepts/meta.json`)

{
	"title": "Concepts",
	"pages": ["volumes", "repositories", "backups", "organizations"]
}

---

### Apps Docs Content Docs Concepts Organizations (`apps/docs/content/docs/concepts/organizations.mdx`)

---
title: Organizations
description: Understand how Zerobyte scopes resources, invitations, and roles
---

Zerobyte is organization-scoped. Repositories, volumes, backup schedules, notification destinations, SSO providers, and invitations all belong to an organization.

## What an organization controls

- Every request runs inside an active organization context.
- Resource lists and mutations are filtered to that active organization.
- Membership determines who can access organization-scoped features.
- The organization ID shown in **Settings > Organization** is the same ID used by provisioning.

## How organizations are created

For a normal local signup, if the new user has no existing membership, Zerobyte creates a default workspace named like `Alice's Workspace` and makes that user an `owner`.

Invited SSO users behave differently: Zerobyte creates their membership in the invited organization during the first successful SSO callback, so they land in that organization instead of getting a separate default workspace.

## Organization roles

| Role | What it means in practice |
| --- | --- |
| `member` | Standard organization member. Can use the organization, but cannot access organization-management actions such as member management or SSO settings. |
| `admin` | Can access the **Organization** settings tab, manage members, manage invitations, toggle SSO auto-linking, and download the active organization's recovery key. |
| `owner` | Has all admin capabilities and is additionally required for registering new SSO providers. Owners cannot be demoted or removed through the current organization member-management endpoints. |

<Callout type="info">
  Zerobyte also has an instance-wide user role named `admin` for global tasks such as registration control and user management. That global role is separate from organization roles like `member`, `admin`, and `owner`.
</Callout>

## Current permission model

Zerobyte does not currently provide fine-grained RBAC for backup operations. Authenticated organization members are trusted operators for the organization's repositories, volumes, backup schedules, restores, and notification destinations.

The `member`, `admin`, and `owner` roles mainly control organization-management actions. For example, standard members cannot manage members, SSO settings, invitations, or recovery-key downloads, but they can still operate backup resources inside the organization.

<Callout type="warn">
  Only add users to an organization if you are comfortable with them managing that organization's backup resources and using the storage, volume, and notification capabilities configured for the Zerobyte instance.
</Callout>

## Invitations

Organization invitations carry four important pieces of data:

- The invitee email address
- The role to grant on acceptance
- The invitation status
- The expiration time

In the current UI, invitation management lives inside **Settings > Organization > Single Sign-On** because SSO access is invite-oriented.

### Role assignment by invitation

New invitations can assign one of these organization roles immediately:

- `member`
- `admin`
- `owner`

That matters because the current **Members** table only lets you toggle existing members between `member` and `admin`. If you want to add another owner, invite that person as an `owner` from the invitation flow.

## Member management behavior

- Organization admins and owners can promote `member -> admin`.
- Organization admins and owners can demote `admin -> member`.
- Owners cannot be demoted through the member-management API.
- Owners cannot be removed through the member-management API.
- Removing a member immediately removes their access to that organization's resources.

If the removed user belongs to another organization, Zerobyte rehomes their active session to a fallback organization membership. If they do not belong to any other organization, Zerobyte revokes their sessions and they must sign in again after being re-added.

## Organization ID

The **Organization ID** shown in **Settings > Organization** is primarily a reference value, but it is important for operator-managed setups because provisioning entries must point at an existing organization ID.

See [Provisioning](/docs/guides/provisioning) if you manage repositories and volumes declaratively.

## Related docs

- [OIDC and Single Sign-On](/docs/guides/oidc-sso)
- [Recovery keys and repository passwords](/docs/guides/recovery-key-and-repository-passwords)

import { Callout } from "fumadocs-ui/components/callout";

---

### Apps Docs Content Docs Concepts Repositories (`apps/docs/content/docs/concepts/repositories.mdx`)

---
title: Repositories
description: Encrypted storage destinations where your backup snapshots are stored
---

Repositories are encrypted storage destinations where your backup snapshots are stored. Every piece of data that Zerobyte backs up ends up in a repository, protected by strong encryption and deduplicated to save space.

## What are repositories?

A repository is a Restic-powered storage location that holds your backup snapshots. When you create a backup job, you point it at a repository, and that is where the encrypted data goes.

Repositories provide:

- **End-to-end encryption**, all data is encrypted before it leaves your machine, so even your storage provider cannot read it
- **Deduplication**, identical data blocks are stored only once, even across different backups
- **Compression**, configurable compression reduces storage costs
- **Immutable snapshots**, once a snapshot is created, it cannot be modified
- **Incremental backups**, only changed data is transferred and stored after the initial backup

Each repository is self-contained. You can store repositories on local disks, cloud object storage, remote servers, or any of 40+ backends supported through rclone.

## Supported repository backends

Zerobyte supports eight backend types. When creating a repository, you select a backend and fill in the required fields in the UI.

### Local

Store backups on a local disk or mounted filesystem. Zerobyte creates the repository directory under the path you provide.

**Use cases:** fast local backups, NAS devices, USB-attached drives, high-speed recovery scenarios.

**Fields:** Name, Path.

<Callout type="info">
  Zerobyte automatically creates a subdirectory under the given path to keep each repository isolated.
</Callout>

### S3-compatible

Store backups on Amazon S3 or any S3-compatible service, including MinIO, Wasabi, Backblaze B2, and DigitalOcean Spaces.

**Use cases:** cloud-native storage, offsite backups, long-term archival.

**Fields:** Name, Endpoint, Bucket, Access Key ID, Secret Access Key.

Sensitive fields are encrypted before storage. See [Credential security](#credential-security) below.

### Cloudflare R2

S3-compatible object storage from Cloudflare with zero egress fees, making it cost-effective for frequent restores.

**Use cases:** cost-effective cloud backups, frequent restore operations, global edge storage.

**Fields:** Name, Endpoint, Bucket, Access Key ID, Secret Access Key.

### Google Cloud Storage

Store backups on Google Cloud Platform's object storage.

**Use cases:** GCP-native storage, integration with existing GCP infrastructure, Nearline/Coldline storage tiers.

**Fields:** Name, Bucket, Project ID, Service Account JSON.

<Callout type="info">
  The service account needs permissions to create, read, and delete objects in the target bucket.
</Callout>

### Azure Blob Storage

Store backups on Microsoft Azure's object storage.

**Use cases:** Azure-native storage, enterprise compliance, Hot/Cool/Archive tier optimization.

**Fields:** Name, Container, Account Name, Account Key, Endpoint Suffix (optional).

### Rclone

Access any of rclone's 40+ supported cloud storage backends, including Dropbox, Google Drive, OneDrive, and many more.

**Use cases:** exotic cloud providers, consumer cloud storage, cross-cloud backup strategies.

**Fields:** Name, Remote (as configured in your rclone config), Path.

<Callout type="warn">
  Your rclone configuration file must be mounted into the Zerobyte container. Consumer services like Google Drive may have API rate limits and are not recommended for production backups.
</Callout>

### REST server

Connect to a Restic REST server for network-based backup storage.

**Use cases:** self-hosted backup infrastructure, dedicated backup servers, REST-based appliances.

**Fields:** Name, URL, Path (optional), Username (optional), Password (optional).

### SFTP

Store backups on remote servers accessible via SSH.

**Use cases:** backup to remote Linux servers, VPS-based offsite storage, SSH-accessible targets.

**Fields:** Name, Host, Port, User, Path, SSH Private Key, Known Hosts or Skip Host Key Verification.

<Callout type="warn">
  For production use, keep host key verification enabled and provide the server's known host key to prevent man-in-the-middle attacks.
</Callout>

## Connecting to an existing repository

When creating a repository, you can mark it as **existing** to connect to a Restic repository that was already initialized elsewhere. This is useful when:

- Another Zerobyte instance already created the repository
- You initialized the repository directly with the Restic CLI
- You are migrating from another backup tool that uses Restic

Zerobyte will verify that it can access the existing repository and begin using it for backups and restores without re-initializing it.

When importing an existing repository, Zerobyte can either use the active organization's recovery key or store a separate custom password for that repository. See [Recovery keys and repository passwords](/docs/guides/recovery-key-and-repository-passwords) for the exact password model and when to choose each option.

## Compression modes

Compression is set when creating a repository and applies to all backups stored in it. Three modes are available:

<Accordions>
  <Accordion title="Auto (default, recommended)">
    Restic automatically decides whether to compress each data chunk. This provides the best balance of speed and space savings for most workloads.
  </Accordion>

  <Accordion title="Off">
    No compression is applied. Use this when backing up data that is already compressed, such as video files, ZIP archives, or compressed images. Avoids wasting CPU on data that will not shrink further.
  </Accordion>

  <Accordion title="Max">
    Maximum compression for the best space savings. Uses more CPU time during backups. Best suited for text-heavy data, source code, and database dumps where CPU is not a bottleneck.
  </Accordion>
</Accordions>

## Bandwidth limiting

You can configure upload and download speed limits per repository to prevent backups from saturating your network. Limits are set in the repository settings with the following units: **Kbps**, **Mbps**, or **Gbps**.

- **Upload limit**, controls the speed at which data is sent to the repository during backups. Useful for avoiding bandwidth contention during business hours or staying within ISP caps.
- **Download limit**, controls the speed at which data is retrieved during restores or snapshot browsing. Helps manage egress costs on cloud providers.

<Callout type="info">
  Bandwidth limits are enforced at the application layer. Actual network usage may be slightly higher due to protocol overhead.
</Callout>

## Repository maintenance

Zerobyte provides maintenance actions in the repository details page.

### Doctor

A background maintenance run that unlocks the repository, runs `restic check`, and repairs the index if restic reports that repair is needed.

<Callout type="warn">
  Doctor blocks other repository operations while it runs, but it does **not** currently perform a full `check --read-data` scrub and it does **not** prune unused data. See the [Repository maintenance guide](/docs/guides/repository-maintenance) for the exact behavior.
</Callout>

### Unlock

Removes stale locks from a repository. Use this when you see "repository is locked" errors after a crash or forced termination of a backup or restore operation.

<Callout type="info">
  Only run unlock if you are certain no other operations are actively accessing the repository.
</Callout>

### Statistics refresh

The refresh button in **Compression Statistics** recalculates stored-size, compression, and snapshot-count metrics. It does not replace Doctor and it does not change repository health status.

## Snapshots

Each backup creates an immutable snapshot in the repository. Snapshots are incremental, so after the initial full backup, only changed data is stored in subsequent snapshots. This means the 10th backup of a 100 GB dataset might only add a few megabytes if little has changed.

You can browse the contents of any snapshot and restore individual files or entire directories from the Zerobyte interface.

## Mirror repositories

You can configure backup jobs to copy snapshots to additional repositories after each successful backup. This provides:

- **Geographic redundancy**, store copies in different regions or data centers
- **Provider diversification**, avoid depending on a single cloud provider
- **Compliance**, meet data locality or multi-copy retention requirements

Mirrors are configured per backup job. See [Backups](/docs/concepts/backups) for details on setting up mirror repositories.

## Credential security

All sensitive fields (passwords, access keys, private keys, service account credentials) are encrypted before being stored. Credentials are never saved in plaintext.

Provisioned repositories also support secret references:

- **`env://VARIABLE_NAME`**, resolves the value from the specified environment variable during provisioning
- **`file://secret_name`**, resolves the value from a Docker secret file at `/run/secrets/secret_name` during provisioning

During provisioning, Zerobyte resolves these references on startup and stores the resolved value encrypted in the database.

The regular repository form in the UI currently expects the actual credential value. Entering `env://...` or `file://...` there will not resolve it at runtime.

## Best practices

<Accordions>
  <Accordion title="Use separate repositories for different data classes">
    Create distinct repositories for critical data, archive data, and test/development environments. This lets you apply different retention policies, storage tiers, and maintenance schedules to each class.
  </Accordion>

  <Accordion title="Enable bandwidth limits for cloud repositories">
    Prevent backup operations from saturating your internet connection, especially during business hours. Even modest limits (e.g., 50 Mbps) can make a significant difference to other services sharing the same link.
  </Accordion>

  <Accordion title="Run maintenance (Doctor) periodically">
    Schedule the Doctor operation monthly or quarterly for production repositories to verify data integrity, rebuild indexes, and reclaim space from deleted snapshots.
  </Accordion>

  <Accordion title="Test restore operations regularly">
    Backups are only as good as your ability to restore from them. Periodically restore files from your repositories to verify that data can be recovered, recovery time meets your expectations, and restored data is complete.
  </Accordion>

  <Accordion title="Monitor repository health">
    Keep an eye on repository status. A repository in an error state will cause backup jobs to fail. Set up notifications so you are alerted to issues before they affect your backup schedule.
  </Accordion>
</Accordions>

## Next steps

<Cards>
  <Card title="Backups" icon={<CalendarDays />} href="/docs/concepts/backups">
    Learn how backup jobs create snapshots and manage retention
  </Card>

  <Card title="Rclone integration" icon={<Cloud />} href="/docs/guides/rclone">
    Set up rclone to access 40+ cloud storage backends
  </Card>

  <Card title="Configuration reference" icon={<Wrench />} href="/docs/configuration">
    Full reference for all environment variables and Docker settings
  </Card>
</Cards>

import { Step, Steps } from "fumadocs-ui/components/steps";
import { Accordion, Accordions } from "fumadocs-ui/components/accordion";
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
import { Card, Cards } from "../../../src/components/DocsCard";
import { Callout } from "fumadocs-ui/components/callout";
import { Cloud, CalendarDays, Wrench } from "lucide-react";

---

### Apps Docs Content Docs Concepts Volumes (`apps/docs/content/docs/concepts/volumes.mdx`)

---
title: Volumes
description: Understand volume types, mounting, and how Zerobyte connects to your data sources
---

Volumes are the data sources you want to protect with Zerobyte. Each volume represents a filesystem, such as a local directory, a network share, or a remote storage location, that Zerobyte mounts and reads from when running backups.

## What are volumes?

A volume tells Zerobyte *where* your data lives. When you create a volume, you provide connection details (a path, a server address, credentials) and Zerobyte takes care of mounting that filesystem, monitoring its health, and making it available to your backup jobs.

Volumes support a range of protocols, from simple local directories to NFS shares, Windows/Samba file servers, WebDAV endpoints, SFTP connections, and cloud storage via rclone. Once a volume is mounted, you can browse its contents directly in the UI, assign it to one or more backup jobs, and let Zerobyte handle the rest.

<Callout type="warn">
  Mounted remote volumes can expose translated metadata instead of the source system's original ownership and ACL model. Before relying on NFS, SMB, WebDAV, SFTP, or rclone volumes for metadata-sensitive backups, read [Mounted Shares, ACLs, and Metadata Fidelity](/docs/guides/mounted-shares-and-acls).
</Callout>

## Supported volume types

Zerobyte supports six volume types. Each one is configured through the web UI when you create or edit a volume.

<Tabs items={["Directory", "NFS", "SMB/CIFS", "WebDAV", "SFTP", "Rclone"]}>
  <Tab value="Directory">
    ### Directory (local)

    A local directory on the host machine, mounted into the Zerobyte container via your `compose.yaml` file.

    **Use cases:**

    - Backing up application data, configuration files, or databases on the host
    - Testing backup configurations before adding remote volumes
    - Protecting Docker volume data or bind-mounted directories

    **Form fields:**

    - **Name**, a descriptive label for this volume
    - **Path**, the path *inside the container* where the directory is mounted (e.g., `/data`)

    <Callout type="info">
      You must first mount the host directory into the Zerobyte container by adding it to the `volumes` section of your `compose.yaml`. For example, to back up `/home/user/photos` on the host, add `- /home/user/photos:/photos:ro` to your compose file, restart the container, then create a Directory volume in the UI with the path `/photos`.
    </Callout>
  </Tab>

  <Tab value="NFS">
    ### NFS (Network File System)

    Mount NFS exports from a NAS, file server, or any system that shares directories over the NFS protocol.

    **Use cases:**

    - Backing up NAS devices (Synology, QNAP, TrueNAS, etc.)
    - Enterprise file server protection
    - Shared storage in Linux environments

    **Form fields:**

    - **Name**, a descriptive label for this volume
    - **Server**, hostname or IP address of the NFS server
    - **Export Path**, the exported directory on the server (e.g., `/volume1/data`)
    - **NFS Version**, protocol version: `3`, `4`, or `4.1`
    - **Port**, NFS port (default: `2049`)
    - **Read Only**, mount the share in read-only mode

    <Callout type="warn">
      NFS volumes require the `SYS_ADMIN` capability in your Docker container configuration. See the [Installation guide](/docs/installation) for details on enabling `cap_add: [SYS_ADMIN]`.
    </Callout>
  </Tab>

  <Tab value="SMB/CIFS">
    ### SMB/CIFS

    Connect to Windows file shares or Samba servers using the SMB/CIFS protocol.

    **Use cases:**

    - Windows file server backups
    - Samba shares on Linux or NAS devices
    - Active Directory-integrated storage

    **Form fields:**

    - **Name**, a descriptive label for this volume
    - **Server**, hostname or IP address of the SMB server
    - **Share**, name of the shared folder (e.g., `documents`)
    - **Guest Mode**, enable unauthenticated access if the share allows it
    - **Username**, account used to connect when guest mode is disabled
    - **Password**, account password when guest mode is disabled
    - **Domain**, Windows domain (optional)
    - **SMB Version**, protocol version: `1.0`, `2.0`, `2.1`, or `3.0`
    - **Port**, SMB port (default: `445`)
    - **Read Only**, mount the share in read-only mode

    <Callout type="info">
      Guest access is supported through the **Guest Mode** option.
    </Callout>

    <Callout type="warn">
      SMB volumes require the `SYS_ADMIN` capability in your Docker container configuration. See the [Installation guide](/docs/installation) for setup details.
    </Callout>
  </Tab>

  <Tab value="WebDAV">
    ### WebDAV

    Mount storage over HTTP/HTTPS using the WebDAV protocol. This is a common option for self-hosted cloud platforms.

    **Use cases:**

    - Nextcloud or ownCloud instances
    - Web-based file storage services
    - Any server that exposes a WebDAV endpoint

    **Form fields:**

    - **Name**, a descriptive label for this volume
    - **Server**, hostname or IP of the WebDAV server
    - **Path**, path on the server (e.g., `/remote.php/dav/files/username`)
    - **Username**, WebDAV account username (optional)
    - **Password**, WebDAV account password (optional)
    - **Port**, server port (e.g., `443` for HTTPS)
    - **SSL**, enable HTTPS connection
    - **Read Only**, mount in read-only mode

    <Callout type="warn">
      WebDAV volumes require the `SYS_ADMIN` capability in your Docker container configuration. See the [Installation guide](/docs/installation) for setup details.
    </Callout>
  </Tab>

  <Tab value="SFTP">
    ### SFTP (SSH File Transfer Protocol)

    Mount remote directories over SSH. Supports both password-based and private key authentication.

    **Use cases:**

    - Remote Linux server backups
    - VPS data protection
    - Any SSH-accessible storage

    **Form fields:**

    - **Name**, a descriptive label for this volume
    - **Host**, hostname or IP of the SSH server
    - **Port**, SSH port (default: `22`)
    - **Username**, SSH account username
    - **Password**, password authentication (optional if using a private key)
    - **Private Key**, SSH private key authentication (optional if using a password)
    - **Path**, directory path on the remote server
    - **Skip Host Key Verification**, disable host key checking (not recommended for production)
    - **Known Hosts**, required unless host key verification is skipped
    - **Read Only**, mount in read-only mode

    <Callout type="warn">
      SFTP volumes require both the `SYS_ADMIN` capability and access to `/dev/fuse` in your Docker container configuration. See the [Installation guide](/docs/installation) for setup details.
    </Callout>
  </Tab>

  <Tab value="Rclone">
    ### Rclone (40+ cloud providers)

    Access cloud storage from providers like Google Drive, Dropbox, OneDrive, Backblaze B2, and many more through rclone.

    **Use cases:**

    - Backing up data stored in cloud services to an encrypted repository
    - Consolidating data from multiple cloud providers
    - Accessing specialized storage backends not natively supported

    **Form fields:**

    - **Name**, a descriptive label for this volume
    - **Remote**, the rclone remote name as defined in your rclone configuration (e.g., `gdrive`)
    - **Path**, path within the remote (e.g., `/documents`)
    - **Read Only**, mount in read-only mode

    <Callout type="info">
      Rclone must be configured on your host first, and the configuration directory must be mounted into the Zerobyte container. See the [Installation guide](/docs/installation#mounting-rclone-configuration) for instructions on mounting your rclone config.
    </Callout>

    <Callout type="warn">
      Rclone volumes require both the `SYS_ADMIN` capability and access to `/dev/fuse` in your Docker container configuration.
    </Callout>
  </Tab>
</Tabs>

## Volume status

Every volume is in one of three states, visible at a glance in the Volumes list.

<Steps>
  <Step>

### Mounted

The volume is connected and accessible. Backup jobs can read from this volume.

  </Step>

  <Step>

### Unmounted

The volume exists in Zerobyte but is not currently connected. Backups that depend on this volume will not run until it is mounted again.

  </Step>

  <Step>

### Error

Something went wrong, the mount failed, the network is unreachable, or credentials were rejected. The volume detail view shows the specific error message. If auto-remount is enabled, Zerobyte will attempt to recover automatically.

  </Step>
</Steps>

## Auto-remount

Auto-remount is enabled by default for every volume. When a mounted volume enters an error state (for example, due to a temporary network outage or a server restart), Zerobyte automatically attempts to re-establish the connection without any manual action.

This is especially valuable for network-based volume types (NFS, SMB, WebDAV, SFTP, Rclone) where transient connectivity issues are common. Auto-remount ensures your scheduled backups continue to run even after brief disruptions.

You can disable auto-remount for any volume if you prefer to handle reconnections manually.

## File browsing

Once a volume is mounted, you can browse its contents directly from the Zerobyte web interface. File browsing lets you:

- Verify that the volume mounted correctly and points to the expected data
- Explore the directory structure to identify paths you want to include or exclude in your backup jobs
- Confirm that the files and folders you need to protect are accessible

This is a read-only view of the volume's contents, browsing does not modify any files.

## Read-only mode

All six volume types support mounting in read-only mode. When enabled, Zerobyte can still read and back up files, but write operations are blocked at the filesystem level.

<Callout type="info">
  Read-only mode adds an extra layer of safety when backing up production data. It guarantees that the backup process cannot accidentally modify or delete source files.
</Callout>

## Credential security

Sensitive fields, such as passwords, private keys, and other secrets, are encrypted before they are stored. Zerobyte never saves credentials in plain text.

Provisioned volumes also support secret references:

- **`env://VARIABLE_NAME`**, resolves the value from an environment variable set in your `compose.yaml` during provisioning
- **`file://secret_name`**, resolves the value from a Docker secrets file at `/run/secrets/secret_name` during provisioning

During provisioning, Zerobyte resolves these references on startup and stores the resolved value encrypted in the database.

The regular volume form in the UI currently expects the actual credential value. Entering `env://...` or `file://...` there will not resolve it at runtime.

<Callout type="info">
  For a complete walkthrough of managing secrets through environment variables, Docker secrets, and provisioning files, see the [Provisioning guide](/docs/guides/provisioning).
</Callout>

## Best practices

<Accordions>
  <Accordion title="Name volumes descriptively">
    Use names that clearly identify what data the volume contains and where it comes from. Good examples:

    - `production-database-dumps`
    - `customer-uploads-nfs`
    - `accounting-smb-share`

    Avoid generic names like `volume1` or `backup`, they become confusing as you add more volumes.
  </Accordion>

  <Accordion title="Enable auto-remount for network volumes">
    Network shares can experience transient failures from server restarts, brief network outages, or DNS hiccups. Keep auto-remount enabled so Zerobyte recovers on its own and your scheduled backups are not disrupted.
  </Accordion>

  <Accordion title="Use read-only mode when possible">
    Mounting volumes in read-only mode prevents any chance of the backup process modifying source data. This is especially important for:

    - Live production systems
    - Archive or compliance data
    - Shared storage used by multiple applications
  </Accordion>

  <Accordion title="Monitor volume health">
    A volume in error state will cause its associated backup jobs to fail. Check the Volumes list regularly. Catching mount issues early prevents gaps in your backup coverage.
  </Accordion>
</Accordions>

## Next steps

<Cards>
  <Card title="Repositories" href="/docs/concepts/repositories">
    Learn about encrypted storage destinations where your backup snapshots are kept
  </Card>

  <Card title="Backups" href="/docs/concepts/backups">
    Configure backup jobs that connect your volumes to repositories with scheduling and retention
  </Card>
</Cards>

import { Step, Steps } from "fumadocs-ui/components/steps";
import { Accordion, Accordions } from "fumadocs-ui/components/accordion";
import { Tab, Tabs } from "fumadocs-ui/components/tabs";

---

### Apps Docs Content Docs Guides 3 2 1 Backup Strategy (`apps/docs/content/docs/guides/3-2-1-backup-strategy.mdx`)

---
title: 3-2-1 Backup Strategy
description: Build a practical 3-2-1 backup setup in Zerobyte with a fast local copy and an offsite encrypted mirror
---

The 3-2-1 backup strategy is a simple rule for reducing backup risk:

- Keep **3 copies** of your data
- Use **2 different storage systems**
- Keep **1 copy offsite**

In Zerobyte, that usually looks like this:

- Your live data on a server, NAS, or workstation
- A primary backup repository on fast local storage
- A mirrored repository on an offsite backend such as S3, Cloudflare R2, Google Cloud Storage, Azure Blob Storage, SFTP, or a remote REST server

## Why 3-2-1 is a good strategy

A single backup copy can still fail for the same reason as the original data. The 3-2-1 rule reduces correlated failure, so one bad delete, dead disk, provider issue, or site-level incident is less likely to wipe out every copy at once.

| Failure scenario | What goes wrong without 3-2-1 | How 3-2-1 helps |
| --- | --- | --- |
| Accidental deletion or bad change | Your only backup may be too recent or incomplete | Older snapshots give you a clean restore point |
| Disk or NAS failure | Data and backup can be lost together | A second storage system still has the data |
| Ransomware or host compromise | Local storage may be affected too | An offsite copy gives you another recovery path |
| Fire, theft, or power event | Everything in one building can disappear together | The offsite copy survives |
| Provider or account problem | One backend outage blocks restore | Different backends reduce that single point of failure |

Zerobyte fits this strategy well because it already gives you the building blocks:

- **Encrypted repositories** so offsite copies stay private
- **Incremental, deduplicated snapshots** so keeping multiple copies is more storage-efficient
- **Mirror repositories** so one backup job can copy snapshots to additional destinations
- **Browsable restores** so you can test recovery without dropping to the CLI

## How 3-2-1 maps to Zerobyte

| 3-2-1 element | Zerobyte example |
| --- | --- |
| **3 copies** | Live data + primary repository + mirror repository |
| **2 different storage systems** | Local disk or NAS for the primary copy, object storage or remote server for the second copy |
| **1 offsite copy** | A repository in another building, region, or provider |

<Callout type="info">
  Historically, the "2" in 3-2-1 meant two different kinds of media. In modern setups, different storage systems and failure domains matter more than literal media type. A local disk plus cloud object storage is a strong practical interpretation.
</Callout>

## How to do it with Zerobyte

<Steps>
  <Step>

### Create the volume for your live data

Add the directory or remote share you want to protect as a [volume](/docs/concepts/volumes).

  </Step>

  <Step>

### Create a primary repository for fast restores

Use a backend that is quick and close to the source, such as a local disk, mounted NAS path, nearby REST server, or nearby SFTP host. This gives you a fast day-to-day restore target.

  </Step>

  <Step>

### Create a second repository in a different failure domain

Create another repository on an offsite backend such as S3-compatible storage, Cloudflare R2, Google Cloud Storage, Azure Blob Storage, or a remote SFTP or REST server in another location.

If you want a strong "2" in 3-2-1, avoid putting both repositories on the same host, in the same rack, or behind the same provider account.

  </Step>

  <Step>

### Create the backup job and add the offsite copy as a mirror

Set the primary repository as the main backup repository, then configure the offsite repository as a mirror repository. After each successful backup, Zerobyte copies that snapshot to the mirror repository automatically.

  </Step>

  <Step>

### Add a schedule and retention policy

Pick a schedule that matches how often the data changes. A solid starting point for many workloads is:

- Schedule: `0 2 * * *` (daily at 2:00 AM)
- Keep Daily: `7`
- Keep Weekly: `4`
- Keep Monthly: `6`
- Keep Yearly: `1`

Adjust this based on your recovery needs and storage budget.

  </Step>

  <Step>

### Store the recovery key outside Zerobyte

Keep the organization's recovery key in a password manager, secret vault, or other secure place outside the Zerobyte server. The offsite copy only helps if you can still decrypt it during an outage.

  </Step>

  <Step>

### Run a manual backup and test restore

Use **Backup now** to create the first snapshot, then test a restore to a non-production path. A backup strategy is only real once you have verified that recovery works.

  </Step>
</Steps>

## Example Zerobyte layout

| Copy | Example |
| --- | --- |
| Live data | `/srv/data` on your server |
| Local backup copy | Local repository on `/mnt/backup-disk/zerobyte` |
| Offsite backup copy | Cloudflare R2 bucket or remote SFTP repository |

One backup job can then write to the local repository and mirror each successful snapshot to the offsite repository.

## Common mistakes

- Keeping the live data and primary repository on the same physical disk
- Calling a second copy "offsite" when it is still in the same building
- Storing every copy under the same provider account without any separation
- Forgetting to protect the recovery key or imported repository password
- Never testing restores

## Related docs

- [Quickstart](/docs/quickstart)
- [Backups](/docs/concepts/backups)
- [Repositories](/docs/concepts/repositories)
- [Recovery keys and repository passwords](/docs/guides/recovery-key-and-repository-passwords)

import { Step, Steps } from "fumadocs-ui/components/steps";
import { Callout } from "fumadocs-ui/components/callout";

---

### Apps Docs Content Docs Guides Backup Webhooks (`apps/docs/content/docs/guides/backup-webhooks.mdx`)

---
title: Backup Webhooks
description: Run HTTP hooks before and after a backup job
---

Backup webhooks let a backup job call an HTTP endpoint immediately before Restic starts and immediately after Restic finishes. Use them when the source needs a short runtime action around the backup, such as pausing a service, creating a database dump, flushing a cache, or resuming a container after the snapshot.

Backup webhooks are configured per backup job in the **Advanced** section. They are different from [notifications](/docs/guides/notifications): notifications report backup events to people or systems, while backup webhooks are part of the backup execution lifecycle.

## How backup webhooks work

Zerobyte supports two lifecycle hooks:

| Hook | When it runs | Failure behavior |
| --- | --- | --- |
| **Pre-backup webhook** | Before Restic starts reading the volume | A failed request stops the backup before Restic runs |
| **Post-backup webhook** | After Restic finishes, fails, or is cancelled | A failed request is recorded with the final result; a clean backup becomes a warning |

Each hook sends a `POST` request. A response with a `2xx` status code is treated as success. Redirects are not followed. Webhook requests time out after `WEBHOOK_TIMEOUT` seconds, which defaults to 60 seconds.

<Callout type="warn">
  Every backup webhook URL must use an origin listed in `WEBHOOK_ALLOWED_ORIGINS`. The origin is the scheme, hostname, and port, such as `http://host.docker.internal:9000`.
</Callout>

## Request body

If the hook body field is empty, Zerobyte sends a JSON backup context body and sets `Content-Type: application/json`.

Pre-backup webhook example:

```json
{
  "phase": "pre",
  "event": "backup.pre",
  "jobId": "job_...",
  "scheduleId": "sched_...",
  "organizationId": "org_...",
  "sourcePath": "data"
}
```

Post-backup webhook example:

```json
{
  "phase": "post",
  "event": "backup.post",
  "jobId": "job_...",
  "scheduleId": "sched_...",
  "organizationId": "org_...",
  "sourcePath": "data",
  "status": "success"
}
```

`status` is only sent to the post-backup webhook. It can be `success`, `warning`, `error`, or `cancelled`. `error` is included on the post-backup webhook when Zerobyte has warning, failure, or cancellation details to report.

If you enter a custom body, Zerobyte sends that exact body instead of the default JSON context. Add a `Content-Type` header yourself if the receiver expects one.

## Headers

Headers are optional and are entered one per line:

```text
X-Zerobyte-Hook-Secret: replace-with-a-long-random-secret
Content-Type: application/json
```

Header values are stored as plain text. Use a scoped webhook secret rather than a reusable account password or long-lived infrastructure token.

## Configure a backup hook

1. Add the webhook origin to `WEBHOOK_ALLOWED_ORIGINS` in the Zerobyte environment.
2. Restart Zerobyte so the environment change is loaded.
3. Open **Backups** and select the backup job.
4. Edit the job and expand **Advanced**.
5. Fill **Pre-backup webhook** or **Post-backup webhook**.
6. Add any required headers.
7. Leave the body empty unless the receiving service requires a custom payload.
8. Save the backup job and run **Backup now** to test the lifecycle.

For Docker Compose on Linux, `host.docker.internal` usually needs an explicit host gateway entry:

```yaml compose.yaml
services:
  zerobyte:
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      - WEBHOOK_ALLOWED_ORIGINS=http://host.docker.internal:9000
```

## How-to: stop and start a Postgres container with adnanh/webhook

This example runs [`adnanh/webhook`](https://github.com/adnanh/webhook) on the Docker host. Zerobyte calls it before and after the backup:

- Pre-backup hook stops the `postgres` container.
- Restic backs up the mounted data.
- Post-backup hook starts the `postgres` container again.

<Callout type="warn">
  Stopping a database container is a blunt consistency strategy. Use it only when a short outage is acceptable. For larger databases, prefer native database dumps, replication snapshots, or storage-level snapshots.
</Callout>

### 1. Install webhook on the Docker host

On Debian or Ubuntu:

```bash
sudo apt-get update
sudo apt-get install webhook
```

`webhook` serves configured hooks at `/hooks/<hook-id>`. The default port is `9000`, and the `-hooks` flag points to the JSON or YAML hook file.

### 2. Create hook scripts

Create a directory for the scripts:

```bash
sudo mkdir -p /opt/zerobyte-hooks
```

Create `/opt/zerobyte-hooks/stop-postgres.sh`:

```sh
#!/bin/sh
set -eu

CONTAINER=postgres

STATE=$(docker inspect -f '{{.State.Running}}' "$CONTAINER")

if [ "$STATE" = "true" ]; then
  docker stop "$CONTAINER"
fi
```

Create `/opt/zerobyte-hooks/start-postgres.sh`:

```sh
#!/bin/sh
set -eu

CONTAINER=postgres

STATE=$(docker inspect -f '{{.State.Running}}' "$CONTAINER")

if [ "$STATE" != "true" ]; then
  docker start "$CONTAINER"
fi
```

Make both scripts executable:

```bash
sudo chmod +x /opt/zerobyte-hooks/stop-postgres.sh /opt/zerobyte-hooks/start-postgres.sh
```

If your container has a different name, change `CONTAINER=postgres` in both scripts.

### 3. Create the webhook config

Create `/opt/zerobyte-hooks/hooks.json`:

```json
[
  {
    "id": "stop-postgres",
    "execute-command": "/opt/zerobyte-hooks/stop-postgres.sh",
    "command-working-directory": "/opt/zerobyte-hooks",
    "http-methods": ["POST"],
    "include-command-output-in-response": true,
    "trigger-rule": {
      "match": {
        "type": "value",
        "value": "replace-with-a-long-random-secret",
        "parameter": {
          "source": "header",
          "name": "X-Zerobyte-Hook-Secret"
        }
      }
    }
  },
  {
    "id": "start-postgres",
    "execute-command": "/opt/zerobyte-hooks/start-postgres.sh",
    "command-working-directory": "/opt/zerobyte-hooks",
    "http-methods": ["POST"],
    "include-command-output-in-response": true,
    "trigger-rule": {
      "match": {
        "type": "value",
        "value": "replace-with-a-long-random-secret",
        "parameter": {
          "source": "header",
          "name": "X-Zerobyte-Hook-Secret"
        }
      }
    }
  }
]
```

Use the same secret in both hook definitions. `include-command-output-in-response` makes `webhook` wait for the script and return an error response if the command fails, which lets Zerobyte stop the backup when the pre-backup hook cannot stop Postgres.

### 4. Start webhook

Run it in the foreground first:

```bash
sudo webhook -hooks /opt/zerobyte-hooks/hooks.json -port 9000 -verbose -http-methods POST
```

In another shell, test both hooks:

```bash
curl -X POST \
  -H "X-Zerobyte-Hook-Secret: replace-with-a-long-random-secret" \
  http://localhost:9000/hooks/stop-postgres

curl -X POST \
  -H "X-Zerobyte-Hook-Secret: replace-with-a-long-random-secret" \
  http://localhost:9000/hooks/start-postgres
```

Once the test works, run `webhook` under your normal process manager.

### 5. Allow Zerobyte to call the webhook server

Add the webhook server origin to Zerobyte:

```yaml compose.yaml
services:
  zerobyte:
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      - WEBHOOK_ALLOWED_ORIGINS=http://host.docker.internal:9000
```

Restart Zerobyte:

```bash
docker compose up -d
```

### 6. Add the hooks to the backup job

Open the backup job in Zerobyte, edit it, and expand **Advanced**.

Use these values:

```text
Pre-backup webhook: http://host.docker.internal:9000/hooks/stop-postgres
Pre-backup webhook headers:
X-Zerobyte-Hook-Secret: replace-with-a-long-random-secret

Post-backup webhook: http://host.docker.internal:9000/hooks/start-postgres
Post-backup webhook headers:
X-Zerobyte-Hook-Secret: replace-with-a-long-random-secret
```

Leave both body fields empty. Zerobyte will send the default JSON context body.

Run **Backup now**. If the stop hook fails or returns a non-`2xx` response, Zerobyte fails the backup before Restic starts. If the start hook fails after Restic finishes, Zerobyte records the problem in the run details so you can restart the container manually.

### 7. Run webhook as a service

After the foreground test works, create a small systemd unit so `webhook` starts on boot.

Create `/etc/systemd/system/zerobyte-webhook.service`:

```ini
[Unit]
Description=Zerobyte backup webhook runner
After=network-online.target docker.service
Wants=network-online.target
Requires=docker.service

[Service]
Type=simple
ExecStart=/usr/bin/webhook -hooks /opt/zerobyte-hooks/hooks.json -port 9000 -http-methods POST -verbose
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
```

Enable and start it:

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now zerobyte-webhook.service
sudo systemctl status zerobyte-webhook.service
```

Check logs with:

```bash
sudo journalctl -u zerobyte-webhook.service -f
```

import { Callout } from "fumadocs-ui/components/callout";

---

### Apps Docs Content Docs Guides Meta.Json (`apps/docs/content/docs/guides/meta.json`)

{
	"title": "Guides",
	"pages": [
		"3-2-1-backup-strategy",
		"restoring",
		"backup-webhooks",
		"performance-tuning",
		"mounted-shares-and-acls",
		"notifications",
		"recovery-key-and-repository-passwords",
		"repository-maintenance",
		"oidc-sso",
		"provisioning",
		"reverse-proxy",
		"tailscale",
		"rclone"
	]
}

---

### Apps Docs Content Docs Guides Mounted Shares And Acls (`apps/docs/content/docs/guides/mounted-shares-and-acls.mdx`)

---
title: Mounted Shares and Permissions
description: A simple guide to what mounted volumes preserve well, what they simplify, and when a local directory is the better fit
---

Mounted volumes are still very useful in Zerobyte. For many people, backing up the file contents is the main goal, and mounted shares work well for that.

Where things get a little more nuanced is permissions and other filesystem details.

<Callout type="info">
  Backing up a mounted share does **not** remove permissions from the source.

  The main question is simpler than it sounds: how much of the source system's permission information can the mounted view actually show to the backup?
</Callout>

## The short version

- If your main goal is recovering files and folders, mounted volumes are often perfectly fine.
- Zerobyte backs up the view that the mounted filesystem presents inside the container.
- Some backends show permissions and ownership very well.
- Some backends show a simplified view.
- If you want the closest thing to a true replica, a local **Directory** volume is the best choice.

## Why this matters

When Zerobyte backs up a remote volume, it does not talk directly to every storage system in its native format.

Instead:

1. Zerobyte mounts the volume inside the Linux container.
2. Linux sees that mounted volume as a normal filesystem.
3. Restic backs up what Linux can see there.

That means the backup is only as detailed as the mounted view.

If the mount shows the real owner, permissions, and advanced permission rules, Restic can back them up.

If the mount shows a simplified version, the snapshot will also contain that simplified version.

## What might be simplified

Depending on the backend, the mounted view may simplify:

- file owner
- group
- read, write, and execute permissions
- advanced permission rules
- extra filesystem metadata

This is why a backup can be excellent for file contents while being less exact for permission details.

## Editing files on a mounted share

This is also useful to know outside of backups.

- Editing an existing file often keeps that file's existing server-side permissions.
- Deleting a file and creating it again usually creates a new remote file.
- A new file usually gets the folder's default or inherited permissions, not necessarily the old file's exact ones.
- Some editors save by replacing the file behind the scenes, so they may behave more like recreate than edit-in-place.

For many users this is not a problem. It mainly matters when you are trying to keep permissions exactly the same.

Common examples where permissions matter more:

- a shared company folder where different teams should see different subfolders
- user home directories where private files should stay private after restore
- website or app files where the service depends on the right owner or executable permissions
- server scripts, cron jobs, or deployment hooks that need to remain executable
- business shares with inherited folder rules, such as finance or HR documents

## Backend comparison

| Backend | Good fit for | Permission fidelity | What to keep in mind |
| --- | --- | --- | --- |
| **Directory** | Local data and bind-mounted host paths | **Best** | Best choice when you want the backup and restore to stay as close as possible to the original filesystem |
| **NFS** | Linux and NAS shares | **Usually good** | Often a strong remote option, but ownership mapping can still vary between systems |
| **SMB/CIFS** | Windows shares and Samba servers | **Mixed to good** | Often the best remote option when permission details matter, but still not a perfect copy of the original Windows permission model |
| **WebDAV** | Nextcloud, ownCloud, and content-focused remote access | **Limited** | Great for backing up files, less reliable for exact permission replication |
| **SFTP** | Secure access to remote Linux servers | **Limited** | Good for content backup, but advanced permission details are often reduced |
| **Rclone** | Cloud providers and object storage | **Lowest** | Best treated as a content source, not as a full filesystem replica |

## What each backend is best at

### Directory

This is the easiest option to trust when you care about both file contents and permissions.

Why it works so well:

- Zerobyte sees the filesystem directly
- there is no extra network protocol translating the data
- restores are usually the most predictable

If your goal is the closest thing to a true replica, choose **Directory** whenever you can.

### NFS

NFS is often a very good fit for Linux-style file sharing.

It is usually a strong choice when:

- the share comes from a Linux or NAS system
- ownership matches well between systems
- you want a remote option that still feels fairly native

It can still vary depending on how the server is configured, so it is good to test with a few real files first.

### SMB/CIFS

SMB is a good choice for Windows shares and Samba servers.

Compared with other remote options, it often gives Linux a richer view of remote permissions. That makes it the most promising remote choice when those details matter.

Still, it helps to think of SMB as "often good" rather than "guaranteed exact."

### WebDAV

WebDAV is very convenient and widely supported.

It is usually best for:

- backing up file contents
- reaching services like Nextcloud and ownCloud
- simple remote access

It is usually **not** the best choice if you want a very exact copy of the original permission model.

### SFTP

SFTP is a solid option when you need secure remote access over SSH.

It is usually good for:

- backing up files from remote Linux servers
- simple remote access with passwords or keys
- content-focused backups

Like WebDAV, it is usually better for file contents than for exact permission replication.

### Rclone

Rclone is excellent for connecting to many cloud providers.

It is usually best for:

- backing up data stored in cloud services
- content-focused backup jobs
- providers that are not traditional filesystems

It is the least suitable option when you want the mounted view to behave like a normal local filesystem.

## Best option for true replication

If by "true replication" you mean:

- same files
- same ownership
- same permissions
- same advanced permission rules
- restore behavior that stays very close to the original

then the best option is still **Directory**.

If the original data lives somewhere else, the most reliable approach is usually one of these:

1. Run Zerobyte close to the data and back it up as a local directory there.
2. Mount the source on the host first, then bind-mount that local path into Zerobyte as a Directory volume.
3. Use remote mounted backends mainly for content backup, not for exact replication.

## Practical advice

1. If you mainly care about recovering files, most mounted backends are fine.
2. If you care a lot about permissions too, prefer **Directory** first.
3. After **Directory**, **NFS** is often the next best choice.
4. **SMB** can also be a good option, especially for Windows and Samba shares, but it is worth testing first.
5. Treat **WebDAV**, **SFTP**, and **rclone** as content-first backends.

<Callout type="info">
  The message here is not "avoid mounted shares." They are useful and often exactly the right choice.

  The simpler rule is: if file contents matter most, mounted shares are often great. If you want the closest possible replica of the original filesystem, a local **Directory** volume is still the best option.
</Callout>

---

### Apps Docs Content Docs Guides Notifications (`apps/docs/content/docs/guides/notifications.mdx`)

---
title: Notifications
description: Create notification destinations, route backup events, and choose the right delivery type
---

Zerobyte's notification system has two layers:

1. A **destination** is a reusable delivery channel such as Slack, SMTP, ntfy, or a webhook.
2. A **backup schedule assignment** decides which events should be sent to which destination.

You create destinations under **Notifications**. You route them per backup schedule from the backup details page.

## How notifications work in Zerobyte

- Destinations are organization-scoped.
- Sensitive destination fields are encrypted before storage.
- Disabled destinations stay saved, but Zerobyte skips them when sending notifications.
- The **Test** button sends a generic test message for that destination.
- Per-schedule routing supports four events: `start`, `success`, `warning`, and `failure`.

<Callout type="info">
  Create destinations once, then reuse them across as many backup schedules as you want.
</Callout>

## Supported destination types

| Type | Best for | Key fields |
| --- | --- | --- |
| `Email (SMTP)` | Existing mail infrastructure, multiple recipients, ticketing aliases | SMTP host, port, sender, recipients, optional auth, TLS |
| `Slack` | Team chat channels | Webhook URL, optional bot username, optional icon emoji |
| `Discord` | Server channels or threads | Webhook URL, optional username, optional avatar URL, optional thread ID |
| `Gotify` | Self-hosted mobile or desktop push | Server URL, app token, priority, optional path |
| `ntfy` | Lightweight publish-subscribe notifications | Topic, optional server URL, optional auth, priority |
| `Pushover` | Personal mobile alerts | User key, API token, optional devices, priority |
| `Telegram` | Group or direct chat notifications | Bot token, chat ID, optional thread ID |
| `Generic Webhook` | Arbitrary HTTP endpoints and automation systems | URL, method, headers, content type, optional JSON template keys |
| `Custom (Shoutrrr URL)` | Power users or services not exposed by the built-in forms | Raw Shoutrrr URL |

<Callout type="warn">
  Outbound HTTP notification destinations must use an origin listed in `WEBHOOK_ALLOWED_ORIGINS`. This applies to Generic Webhook, Gotify, self-hosted ntfy servers, and Custom Shoutrrr URLs that resolve to a custom HTTP endpoint.
</Callout>

## Which type should I use?

- Choose **Email** when you already trust an SMTP relay and want easy fan-out to multiple recipients.
- Choose **Slack** or **Discord** when backup events belong in a shared team room.
- Choose **Gotify** or **ntfy** when you prefer self-hosted push.
- Choose **Pushover** or **Telegram** when you want fast personal alerts on a phone.
- Choose **Generic Webhook** when another system expects a normal HTTP request.
- Choose **Custom (Shoutrrr URL)** when the built-in types are close but not flexible enough, or when you need a different Shoutrrr-supported service entirely.

## Create a destination

1. Open **Notifications** in the sidebar.
2. Click **Create Destination**.
3. Pick the notification type.
4. Fill in the type-specific settings.
5. Save the destination.
6. Open the destination and use **Test** before attaching it to production schedules.

The type is effectively fixed after creation in the current UI, so create a new destination instead of trying to repurpose an existing one from Slack to Email or similar.

## Route notifications per backup schedule

1. Open the backup schedule you want to configure.
2. In the **Notifications** section, click **Add notification**.
3. Select an existing destination.
4. Choose which events should trigger that destination.
5. Save the backup schedule notification settings.

Each assignment is independent. One schedule can send failures to Email, warnings to Slack, and successes nowhere.

## Backup event meanings

| Event | When Zerobyte sends it | What is included |
| --- | --- | --- |
| `start` | The backup begins running | Volume name, repository name, and schedule name |
| `success` | The backup completes cleanly | Volume, repository, schedule, and backup summary details when available |
| `warning` | The backup completes with warnings instead of a clean success | Success details plus warning text when available |
| `failure` | The backup fails validation or execution | Volume, repository, schedule, and the error text |

## Type reference

<Tabs
  items={[
    "Email (SMTP)",
    "Slack",
    "Discord",
    "Gotify",
    "ntfy",
    "Pushover",
    "Telegram",
    "Generic Webhook",
    "Custom",
  ]}
>
  <Tab value="Email (SMTP)">
    Use Email when you want alerts to go to people, shared mailboxes, or downstream systems that already process email.

    **Fields**

    - `SMTP Host`
    - `SMTP Port`
    - `Username` and `Password` (optional)
    - `From Address`
    - `From Name` (optional)
    - `To Addresses`
    - `Use TLS`

    **Notes**

    - `To Addresses` is a comma-separated list in the UI.
    - TLS is a simple yes or no toggle.
    - SMTP authentication is optional because some relays accept trusted network senders.

    **Example**

    ```text
    Type: Email (SMTP)
    SMTP Host: smtp.example.com
    SMTP Port: 587
    Username: alerts@example.com
    Password: <smtp-password>
    From Address: alerts@example.com
    From Name: Zerobyte Alerts
    To Addresses: ops@example.com, backups@example.com
    Use TLS: On
    ```
  </Tab>

  <Tab value="Slack">
    Use Slack for shared operational visibility in a team channel.

    **Fields**

    - `Webhook URL`
    - `Bot Username` (optional)
    - `Icon Emoji` (optional)

    **Example**

    ```text
    Type: Slack
    Webhook URL: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXX
    Bot Username: Zerobyte
    Icon Emoji: :floppy_disk:
    ```
  </Tab>

  <Tab value="Discord">
    Use Discord when your team lives in a server channel, or when you want to post to a specific thread.

    **Fields**

    - `Webhook URL`
    - `Bot Username` (optional)
    - `Avatar URL` (optional)
    - `Thread ID` (optional)
  </Tab>

  <Tab value="Gotify">
    Use Gotify when you want self-hosted push with explicit priority control.

    **Fields**

    - `Server URL`
    - `App Token`
    - `Priority` from `0` to `10`
    - `Path` (optional)

    **Notes**

    - Add the Gotify server origin, such as `https://gotify.example.com`, to `WEBHOOK_ALLOWED_ORIGINS`.
  </Tab>

  <Tab value="ntfy">
    Use ntfy when you want a simple topic-based push service, either on `ntfy.sh` or your own server.

    **Fields**

    - `Server URL` (optional, leave empty for `ntfy.sh`)
    - `Topic`
    - `Username` and `Password` (optional)
    - `Access token` (optional)
    - `Priority`

    **Notes**

    - Leave `Server URL` empty to use `ntfy.sh`.
    - Add self-hosted ntfy origins, such as `https://ntfy.example.com`, to `WEBHOOK_ALLOWED_ORIGINS`.
    - If you set an access token, it takes precedence over username and password.
    - Priority values are `max`, `high`, `default`, `low`, and `min`.

    **Example**

    ```text
    Type: Ntfy
    Server URL: https://ntfy.example.com
    Topic: zerobyte-backups
    Access token: <token>
    Priority: high
    ```
  </Tab>

  <Tab value="Pushover">
    Use Pushover for direct, personal push notifications.

    **Fields**

    - `User Key`
    - `API Token`
    - `Devices` (optional)
    - `Priority` as `-1`, `0`, or `1`
  </Tab>

  <Tab value="Telegram">
    Use Telegram when you want notifications in a bot chat or a group topic.

    **Fields**

    - `Bot Token`
    - `Chat ID`
    - `Thread ID` (optional)

    **Example**

    ```text
    Type: Telegram
    Bot Token: 123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
    Chat ID: -1231234567890
    Thread ID: 3
    ```
  </Tab>

  <Tab value="Generic Webhook">
    Use Generic Webhook when another service expects a normal HTTP request instead of a provider-specific webhook format.

    **Fields**

    - `Webhook URL`
    - `Method` as `GET` or `POST`
    - `Content Type`
    - `Headers`
    - `Use JSON Template`
    - `Title Key` and `Message Key` when JSON mode is enabled

    **Notes**

    - Add the webhook origin, such as `https://hooks.example.com`, to `WEBHOOK_ALLOWED_ORIGINS`.
    - Headers are entered one per line as `Key: Value`.
    - With JSON mode enabled, Zerobyte sends a body shaped like `{titleKey: "...", messageKey: "..."}`.
    - With JSON mode disabled, the request body is plain notification text.

    **Example**

    ```text
    Type: Generic Webhook
    Webhook URL: https://hooks.example.com/backup-events
    Method: POST
    Content Type: application/json
    Headers:
    Authorization: Bearer <token>
    X-Source: zerobyte
    Use JSON Template: On
    Title Key: title
    Message Key: message
    ```
  </Tab>

  <Tab value="Custom">
    Use Custom when you already know the exact Shoutrrr URL you want Zerobyte to use.

    See the [Shoutrrr documentation](https://shoutrrr.nickfedor.com/services/overview) for the supported URL formats and service-specific options.

    If the Shoutrrr URL targets a custom HTTP endpoint, add that endpoint origin to `WEBHOOK_ALLOWED_ORIGINS`.

    **Fields**

    - `Shoutrrr URL`

    **Example**

    ```text
    slack://hook:T000-B000-XXX@webhook?username=Zerobyte
    ```
  </Tab>
</Tabs>

## Practical routing patterns

| Goal | Suggested routing |
| --- | --- |
| Only wake someone up for real problems | `failure` to Email or Pushover |
| Keep the team informed without noise | `warning` and `failure` to Slack or Discord |
| Audit every run | `start`, `success`, `warning`, and `failure` to Email or Generic Webhook |
| Mobile-first personal alerting | `failure` to Telegram, ntfy, Gotify, or Pushover |
| Feed another incident or workflow system | `failure` and optionally `warning` to Generic Webhook |

## Related docs

- [Backups](/docs/concepts/backups)
- [Repository maintenance and status](/docs/guides/repository-maintenance)

import { Callout } from "fumadocs-ui/components/callout";
import { Tab, Tabs } from "fumadocs-ui/components/tabs";

---

### Apps Docs Content Docs Guides Oidc Sso (`apps/docs/content/docs/guides/oidc-sso.mdx`)

---
title: SSO
description: Register OIDC providers, control access with invitations, and understand account-linking behavior
---

Zerobyte's current SSO implementation is organization-scoped and OIDC-based. You register providers under **Settings > Organization > Single Sign-On**, or directly at `/settings/sso/new`.

<Callout type="warn">
  Zerobyte SSO is invite-oriented. Enabling auto-linking does **not** mean "anyone with that email can join".
</Callout>

## What Zerobyte supports

- Each SSO provider belongs to exactly one organization.
- The login page exposes registered providers as `Log in with {providerId}` buttons.
- Organization owners can register new providers.
- Organization owners and admins can manage invitations, toggle per-provider auto-linking, and delete existing providers.
- Zerobyte currently registers OIDC providers with the `openid`, `email`, and `profile` scopes.

## Required values

| Zerobyte field | What it means | Example |
| --- | --- | --- |
| `Provider ID` | Stable unique identifier for this provider. It is used in the callback URL and is what users see on the login button. | `acme-oidc` |
| `Organization Domain` | Domain recorded with the provider and used for provider discovery metadata. | `example.com` |
| `Issuer URL` | The OIDC issuer published by your identity provider. | `https://idp.example.com/realms/acme` |
| `Discovery Endpoint` | The provider's `.well-known/openid-configuration` URL. | `https://idp.example.com/.well-known/openid-configuration` |
| `Client ID` | OIDC client identifier from your IdP. | `zerobyte` |
| `Client Secret` | OIDC client secret from your IdP. | `(secret)` |
| `Link matching emails to existing accounts` | Per-provider trust switch for matching an existing Zerobyte account by email. | `Off` by default |

## Callback URL

Register this exact redirect URI in your identity provider:

```text
${BASE_URL}/api/auth/sso/callback/${providerId}
```

Example:

```text
https://backup.example.com/api/auth/sso/callback/acme-oidc
```

## Setup checklist

1. In your identity provider, create a confidential or web OIDC client for Zerobyte.
2. Register the callback URL above as an allowed redirect URI.
3. Make sure the provider returns `email` and an email-verification claim.
4. In Zerobyte, open **Settings > Organization > Single Sign-On** and click **Register new**.
5. Fill in the provider details and save.
6. Decide whether `Link matching emails to existing accounts` should be enabled for this provider.
7. Invite users before asking them to sign in, unless their existing local account already belongs to the organization.

## How access is decided

The important rule is that Zerobyte checks **organization membership** and **pending invitations** in addition to whatever your identity provider says.

| Situation | Result | Why |
| --- | --- | --- |
| Brand-new user, no membership, no invitation | Blocked | There is no valid organization membership or pending invitation. |
| Brand-new user with a pending invitation | Allowed | Zerobyte creates the membership on first successful SSO login and marks the invitation as `accepted`. |
| Existing local account already belongs to the organization, auto-linking is `off` | Blocked with an account-linking error | The provider is not trusted to auto-link by email yet. |
| Existing local account already belongs to the organization, auto-linking is `on` | Allowed | The provider is trusted for matching-email account linking inside that organization. |
| Existing local account has the same email, has a pending invitation, but is **not already a member** of the organization | Blocked with an account-linking error | Zerobyte refuses to merge an existing account that is outside the organization, even if the email matches and there is a pending invitation. |
| User was invited before, signed in once, was later removed from the organization, then tries SSO again | Blocked | The old invitation remains `accepted`; it is not reopened automatically after removal. |

In practice, an **eligible local account** means an account that already belongs to the organization you are signing into. A matching email plus a pending invitation is **not** enough to merge an unrelated existing account.

## Invitations and membership lifecycle

- Invitations are email-based, organization-specific, and role-bearing.
- A successful first SSO login for an invited user creates the membership and marks the invitation as `accepted`.
- Pending invitations must still be unexpired to work.
- Expired, cancelled, or already accepted invitations do not grant first-time access.
- If a user is removed later, create a **new** invitation before they try SSO again.

The invitation UI lets you assign `member`, `admin`, or `owner` at invite time. The accepted membership is created with that invited role.

<Callout type="warn">
  Zerobyte does not currently provide fine-grained RBAC for backup operations. Invited members can operate backup resources inside the organization. Roles mainly restrict organization-management actions such as SSO settings, invitations, member management, and recovery-key downloads.
</Callout>

## Auto-linking semantics

- Auto-linking is stored **per provider**, not globally.
- Turning it on for one provider does not affect other providers.
- It only applies when the IdP email matches an existing Zerobyte account.
- It does **not** grant organization membership by email alone.
- It does **not** override invite-only access for brand-new users.
- It does **not** merge a matching account that is outside the organization.
- Leaving it off is the safest default for new rollouts.

<Callout type="info">
  The safest mental model is: auto-linking can help an already eligible account add SSO, but it is not an open-enrollment feature.
</Callout>

## Common failure cases

| What the user sees | Typical cause | What to do |
| --- | --- | --- |
| `Access is invite-only. Ask an organization admin to send you an invitation before signing in with SSO.` | No pending invitation, invitation expired, invitation was cancelled, or the user was removed after an earlier accepted invite | Create a new pending invitation or restore membership before retrying. |
| `SSO sign-in was blocked because this email already belongs to another user in this instance.` | Zerobyte found an existing account with the same email, but that account is not eligible for auto-linking with this provider | Confirm whether the account already belongs to the organization and whether auto-linking should be enabled on this trusted provider. |
| `Your identity provider did not mark your email as verified.` | The IdP did not send a verified email claim | Fix email verification in the IdP before retrying. |
| `You have been banned from this application.` | The user is banned at the application level | Remove the ban or use a different account. |
| `SSO authentication failed. Please try again.` | Generic provider or callback failure | Check IdP redirect URI, client credentials, issuer, and discovery endpoint. |

## Related docs

- [Organizations, members, and roles](/docs/concepts/organizations)
- [Recovery keys and repository passwords](/docs/guides/recovery-key-and-repository-passwords)

import { Callout } from "fumadocs-ui/components/callout";

---

### Apps Docs Content Docs Guides Performance Tuning (`apps/docs/content/docs/guides/performance-tuning.mdx`)

---
title: Performance Tuning
description: Reduce CPU, network, and disk pressure from backups and restores
---

import { Callout } from "fumadocs-ui/components/callout";

Most performance tuning is a trade between CPU, network bandwidth, source disk I/O, and repository storage. This guide covers the Zerobyte settings and Restic flags that are safe to adjust.

Change one setting at a time, then compare runtime, CPU, memory, network usage, source disk pressure, and repository behavior. A setting that helps one source can make another source worse.

## Quick recipes

| Goal                                      | Recommended setting                                            | Trade-off                                     |
| ----------------------------------------- | -------------------------------------------------------------- | --------------------------------------------- |
| Lower CPU usage for all Restic operations | Set `GOMAXPROCS=1` or `GOMAXPROCS=2` on the Zerobyte container | Backups, restores, and checks may take longer |
| Lower CPU usage for one repository        | Set repository compression mode to `off`                       | Uses more storage and may upload more data    |
| Reduce source disk pressure               | Add `--read-concurrency 1` to the backup schedule              | Lower throughput on fast storage              |
| Improve throughput on fast storage        | Try `--read-concurrency 4` or `--read-concurrency 8`           | More source I/O and CPU pressure              |
| Avoid saturating the network              | Set repository upload/download limits                          | Longer backup or restore runtime              |
| Avoid backing up very large files         | Add `--exclude-larger-than 10G` to the backup schedule         | Files above the limit are skipped             |

## Where to tune

Some settings apply globally, some apply to one repository, and some apply only to one backup schedule.

| Scope                  | Setting                                                                 | Use it for                                                                 |
| ---------------------- | ----------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| Zerobyte container     | `GOMAXPROCS`                                                            | Capping CPU available to all Restic operations: backups, restores, checks   |
| Repository settings    | Compression mode, upload limit, download limit                          | Repository-wide storage, CPU, and network trade-offs                       |
| Backup schedule        | **Advanced** > **Custom restic parameters**                             | Per-source backup behavior such as read concurrency, pack size, scan mode   |

Custom Restic parameters are appended to the `restic backup` command for that schedule only. Enter one flag per line.

Example low-impact schedule:

```text
--read-concurrency 1
--exclude-larger-than 10G
--no-scan
```

Example faster schedule for a local NVMe source:

```text
--read-concurrency 8
```

## Limit Restic CPU usage with GOMAXPROCS

The standard Go environment variable `GOMAXPROCS` limits how many operating system threads can execute Go code at the same time. Add it to the Zerobyte container environment:

```yaml compose.yaml
services:
  zerobyte:
    environment:
      - GOMAXPROCS=2
```

Then restart Zerobyte:

```bash
docker compose up -d
```

Use these starting points:

| Host                           | Suggested value                                 |
| ------------------------------ | ----------------------------------------------- |
| Small NAS or low-power mini PC | `1`                                             |
| Shared home server             | `2`                                             |
| Dedicated backup box           | Leave unset, or use `4` if you still want a cap |

## Choose compression mode

Repository compression mode is configured on the repository form. Zerobyte passes it to Restic backups as `--compression`.

| Mode   | When to use it                                                                | Trade-off                                   |
| ------ | ----------------------------------------------------------------------------- | ------------------------------------------- |
| `off`  | CPU is the bottleneck, data is already compressed, or storage is cheap        | Larger repositories and more upload traffic |
| `auto` | General-purpose default                                                       | Balanced CPU and storage usage              |
| `max`  | Storage or upload bandwidth is expensive and backup runtime is less important | Highest CPU usage                           |

Good candidates for `off` include video libraries, JPEG/HEIC photo archives, already-compressed archives such as `.zip`, `.7z`, `.gz`, and `.zst`, VM images, and application data that is compressed before Zerobyte sees it.

Good candidates for `max` include text-heavy project directories, logs, and slow or expensive remote storage links where CPU is available.

Compression mode affects new backup data. It does not rewrite or recompress existing packs already stored in the repository.

## Limit upload and download bandwidth

Repository settings include upload and download limits. These map to Restic's `--limit-upload` and `--limit-download` options and apply to operations for that repository.

Use bandwidth limits when backups make the internet connection unusable, a remote storage provider throttles or disconnects large transfers, or restores should not starve other services on the same host.

Prefer the repository bandwidth fields over custom Restic parameters. The UI uses `Kbps`, `Mbps`, and `Gbps`; Zerobyte converts those values to the unit Restic expects.

## Tune pack size

Restic stores data in pack files. The `--pack-size` option changes the target pack size in MiB. Restic's default target is 16 MiB.

Larger pack files can improve performance when the bottleneck is per-file or per-object overhead rather than raw throughput. This is common with very large repositories with many pack files, backends with file count or object count pressure, Google Drive or similar rclone-backed storage, OpenStack Swift-like object storage, and local HDD repositories where creating many small files is expensive.

Suggested starting points:

| Situation                                         | Try               |
| ------------------------------------------------- | ----------------- |
| Slow HDD or small NAS repository                  | `--pack-size 32`  |
| Large repository on object storage or rclone      | `--pack-size 64`  |
| Very large repository with high upload speed      | `--pack-size 128` |

Example:

```text
--pack-size 64
```

Do not jump straight to very large values. Larger packs reduce object count, but each pack is a larger unit to upload, retry, rewrite, and cache. If an upload fails near the end of a large pack, more work may need to be repeated.

<Callout type="info">
	In Zerobyte, `--pack-size` in custom Restic parameters affects only the backup schedule where you set it. It does not rewrite existing repository data and it does not automatically apply to every other schedule.
</Callout>

## Tune read concurrency

`--read-concurrency` controls how many files Restic reads in parallel during backup. It is one of the most useful knobs for slow machines because it directly changes pressure on the source filesystem.

| Source type                     | Suggested value |
| ------------------------------- | --------------- |
| USB HDD, SD card, low-power NAS | `1`             |
| Network mount or FUSE source    | `1` or `2`      |
| SATA SSD                        | `2` or `4`      |
| NVMe SSD                        | `4` or `8`      |

Use low values when the machine is CPU or disk constrained:

```text
--read-concurrency 1
```

Use higher values only when the source can keep up:

```text
--read-concurrency 4
```

If the backup host becomes sluggish, lower `--read-concurrency` before changing pack size. If Restic is idle waiting on storage, increase it one step and compare the next run.

## Keep the cache local

Restic uses a local cache for repository metadata. Zerobyte places this cache in the configured Restic cache directory, which defaults to `/var/lib/zerobyte/restic/cache` inside the container.

Keep the cache on local storage. Do not place `/var/lib/zerobyte` or the Restic cache on a network share. A slow cache can make snapshots, checks, forget operations, and backups feel much slower than the repository itself.

Avoid these flags unless you are diagnosing a cache problem:

| Flag              | What it does                           | Why it can hurt performance                               |
| ----------------- | -------------------------------------- | --------------------------------------------------------- |
| `--no-cache`      | Disables Restic's local metadata cache | Forces Restic to fetch metadata from the repository again |
| `--cleanup-cache` | Removes old cache directories          | Can discard cache data useful to later operations         |

If a repository behaves strangely after moving storage or changing credentials, `--cleanup-cache` can be useful for one diagnostic run. Do not leave it in a normal schedule as a performance tweak.

## Skip progress scanning

By default, Restic scans the source to estimate total work and provide progress information. On very large trees, slow network mounts, or filesystems with high directory traversal cost, that initial scan can be expensive.

Use `--no-scan` to skip the initial scan:

```text
--no-scan
```

This can make a backup start doing useful work sooner, but progress reporting may be less complete. It does not reduce the actual file content Restic must read to create the backup.

## Baselines

For a low-power NAS, mini PC, or shared server, start with this conservative setup:

1. Set `GOMAXPROCS=1` or `GOMAXPROCS=2` on the Zerobyte container.
2. Use repository compression mode `auto` for mixed data, or `off` for mostly compressed media.
3. Add `--read-concurrency 1` to heavy backup schedules.
4. Keep the Restic cache on local storage.
5. If the repository creates too many small objects or files, test `--pack-size 32` on one schedule.

For S3-compatible storage, rclone remotes, and other object stores where each uploaded object has overhead:

1. Keep compression at `auto` unless CPU is clearly the bottleneck.
2. Test `--pack-size 64` on one backup schedule.
3. If object count is still a problem and uploads are reliable, test `--pack-size 128`.
4. Use repository upload/download limits if the link is shared with other services.
5. Avoid `--no-cache`; metadata round trips are often the slow part on remote repositories.

## Troubleshooting

For high CPU usage, use this order:

1. Check whether compression mode is `max`. If yes, switch to `auto` or `off` and run the next backup.
2. Set `GOMAXPROCS=2` on the Zerobyte container and restart.
3. If the source disk is also busy, add `--read-concurrency 1` to the affected backup schedule.
4. If the repository is remote and uploads saturate the host, add a repository upload limit.
5. Watch a few scheduled runs before making further changes.

If backups are still too heavy, reduce the schedule frequency or split very large sources into separate schedules so the heavy work does not run all at once.

For slow backups, use this order:

1. Make sure the source and repository are not on the same slow disk.
2. Use compression mode `auto` before trying `max`.
3. If the source is fast SSD/NVMe storage, try `--read-concurrency 4`.
4. If the bottleneck is network upload, keep compression enabled and remove upload limits during a test window.
5. Check repository storage latency. Cloud, rclone, and network filesystems can be much slower than local disk.

A faster backup that starves the NAS, VM host, or network may be worse than a slower predictable one.

---

### Apps Docs Content Docs Guides Provisioning (`apps/docs/content/docs/guides/provisioning.mdx`)

---
title: Provisioned Resources
description: Manage repositories and volumes through a configuration file with secret references
---

import { Step, Steps } from "fumadocs-ui/components/steps";
import { Callout } from "fumadocs-ui/components/callout";

Zerobyte can sync operator-managed repositories and volumes from a JSON configuration file at startup. This is useful when you want credentials and connection details to live in deployment-time configuration instead of being entered through the UI.

## What Provisioning Supports Today

Provisioning currently supports:

- repositories
- volumes
- secret references through `env://` and `file://`
- updating managed resources in place by keeping the same `id`
- deleting managed resources with `"delete": true`

Provisioning does **not** currently support:

- backup jobs or schedules
- notification destinations
- exporting UI-created resources back into provisioning JSON
- live reloading without restarting the Zerobyte container

<Callout type="warn">
	Provisioning is limited to repositories and volumes in schema version `1`. If you need to manage backup jobs or
	notifications, create them through the UI or API for now.
</Callout>

## Prerequisites

- A running Zerobyte instance with an organization ID
- The `PROVISIONING_PATH` environment variable pointing to your JSON file

### Find your organization ID

1. Sign in to Zerobyte and switch to the organization you want to provision into
2. Open **Settings**
3. Open the **Organization** tab
4. Copy the read-only **Organization ID** value from **Organization Details**

<Callout type="info">
	The **Organization** tab is only visible to organization `admin` and `owner` members. If you do not see it, ask an
	organization admin or owner for the ID, or have them grant you the required role first.
</Callout>

## Quick Start

<Steps>
<Step>

### Create the provisioning file

Create a `provisioning.json` file:

```json provisioning.json
{
	"version": 1,
	"repositories": [
		{
			"id": "local-repo",
			"organizationId": "your-organization-id",
			"name": "Primary Local Repository",
			"backend": "local",
			"compressionMode": "auto",
			"config": {
				"backend": "local",
				"path": "/var/lib/zerobyte/repositories/primary",
				"isExistingRepository": false
			}
		}
	],
	"volumes": [
		{
			"id": "documents",
			"organizationId": "your-organization-id",
			"name": "Documents",
			"backend": "directory",
			"autoRemount": true,
			"config": {
				"backend": "directory",
				"path": "/data/documents"
			}
		}
	]
}
```

</Step>
<Step>

### Mount the file and configure Zerobyte

Mount the provisioning file and set `PROVISIONING_PATH`:

```yaml compose.yaml
services:
  zerobyte:
    image: ghcr.io/nicotsx/zerobyte:v0.43
    environment:
      - PROVISIONING_PATH=/config/provisioning.json
    volumes:
      - ./provisioning.json:/config/provisioning.json:ro
      - /var/lib/zerobyte:/var/lib/zerobyte
      - /srv/documents:/data/documents
```

</Step>
<Step>

### Start or restart Zerobyte

```bash
docker compose up -d
```

On startup, Zerobyte reads the provisioning file, resolves supported secret references, encrypts the resolved secret values, and syncs the managed resources into the database.

</Step>
</Steps>

## Root File Format

The root object always has the same shape:

```json
{
	"version": 1,
	"repositories": [],
	"volumes": []
}
```

| Field          | Required | Notes                                               |
| -------------- | -------- | --------------------------------------------------- |
| `version`      | yes      | Must be `1`                                         |
| `repositories` | no       | Array of provisioned repositories. Defaults to `[]` |
| `volumes`      | no       | Array of provisioned volumes. Defaults to `[]`      |

## Repository Entry Reference

Each item in `repositories` uses this top-level shape:

```json
{
	"id": "repo-id",
	"organizationId": "org-id",
	"name": "Repository Name",
	"backend": "local",
	"compressionMode": "auto",
	"delete": false,
	"config": {
		"backend": "local",
		"path": "/var/lib/zerobyte/repositories/repo-id"
	}
}
```

| Field             | Required | Notes                                                                                                                              |
| ----------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `id`              | yes      | Stable provisioning identifier inside the organization. Keep this stable if you want Zerobyte to update the same resource in place |
| `organizationId`  | yes      | Must match an existing organization                                                                                                |
| `name`            | yes      | Display name in Zerobyte                                                                                                           |
| `backend`         | yes      | Must be one of `local`, `s3`, `r2`, `gcs`, `azure`, `rclone`, `rest`, `sftp`                                                       |
| `compressionMode` | no       | `off`, `auto`, or `max`                                                                                                            |
| `delete`          | no       | Defaults to `false`                                                                                                                |
| `config`          | yes      | Backend-specific object. `config.backend` must match the top-level `backend` value                                                 |

### Repository Shared Config Fields

These fields are available inside every repository `config` object:

| Field                  | Required | Notes                                                                                                                                                                               |
| ---------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `isExistingRepository` | no       | When `true`, Zerobyte treats the repository as already initialized. When omitted or `false`, a newly created provisioned repository is initialized with `restic init` on first sync |
| `customPassword`       | no       | Overrides the organization-level repository password                                                                                                                                |
| `cacert`               | no       | Custom CA certificate contents                                                                                                                                                      |
| `insecureTls`          | no       | Disables TLS verification for supported backends                                                                                                                                    |
| `uploadLimit`          | no       | Bandwidth limit object                                                                                                                                                              |
| `downloadLimit`        | no       | Bandwidth limit object                                                                                                                                                              |

Bandwidth limit objects use this shape:

```json
{
	"enabled": true,
	"value": 10,
	"unit": "Mbps"
}
```

`unit` must be one of `Kbps`, `Mbps`, or `Gbps`.

### Repository Backends

<Tabs items={["Local", "S3", "R2", "GCS", "Azure", "Rclone", "REST", "SFTP"]}>
  <Tab value="Local">
    | Field  | Required | Notes                              |
    | ------ | -------- | ---------------------------------- |
    | `path` | yes      | Path inside the Zerobyte container |

    ```json
    {
    	"backend": "local",
    	"path": "/var/lib/zerobyte/repositories/primary",
    	"isExistingRepository": false
    }
    ```

  </Tab>

  <Tab value="S3">
    | Field             | Required | Notes                               |
    | ----------------- | -------- | ----------------------------------- |
    | `endpoint`        | yes      | Example: `https://s3.amazonaws.com` |
    | `bucket`          | yes      | Bucket name                         |
    | `accessKeyId`     | yes      | Supports secret references          |
    | `secretAccessKey` | yes      | Supports secret references          |

    ```json
    {
    	"backend": "s3",
    	"endpoint": "https://s3.amazonaws.com",
    	"bucket": "company-backups",
    	"accessKeyId": "env://AWS_ACCESS_KEY_ID",
    	"secretAccessKey": "file://aws_secret_access_key",
    	"isExistingRepository": true
    }
    ```

  </Tab>

  <Tab value="R2">
    | Field             | Required | Notes                          |
    | ----------------- | -------- | ------------------------------ |
    | `endpoint`        | yes      | Your Cloudflare R2 S3 endpoint |
    | `bucket`          | yes      | Bucket name                    |
    | `accessKeyId`     | yes      | Supports secret references     |
    | `secretAccessKey` | yes      | Supports secret references     |

    ```json
    {
    	"backend": "r2",
    	"endpoint": "https://<account-id>.r2.cloudflarestorage.com",
    	"bucket": "zerobyte-backups",
    	"accessKeyId": "env://R2_ACCESS_KEY_ID",
    	"secretAccessKey": "env://R2_SECRET_ACCESS_KEY",
    	"isExistingRepository": true
    }
    ```

  </Tab>

  <Tab value="GCS">
    | Field             | Required | Notes                                            |
    | ----------------- | -------- | ------------------------------------------------ |
    | `bucket`          | yes      | Bucket name                                      |
    | `projectId`       | yes      | Google Cloud project ID                          |
    | `credentialsJson` | yes      | Service account JSON. Supports secret references |

    ```json
    {
    	"backend": "gcs",
    	"bucket": "zerobyte-backups",
    	"projectId": "my-gcp-project",
    	"credentialsJson": "file://gcs_credentials_json",
    	"isExistingRepository": true
    }
    ```

  </Tab>

  <Tab value="Azure">
    | Field            | Required | Notes                      |
    | ---------------- | -------- | -------------------------- |
    | `container`      | yes      | Blob container name        |
    | `accountName`    | yes      | Storage account name       |
    | `accountKey`     | yes      | Supports secret references |
    | `endpointSuffix` | no       | Custom endpoint suffix     |

    ```json
    {
    	"backend": "azure",
    	"container": "zerobyte-backups",
    	"accountName": "storageaccount",
    	"accountKey": "env://AZURE_STORAGE_ACCOUNT_KEY",
    	"endpointSuffix": "core.windows.net",
    	"isExistingRepository": true
    }
    ```

  </Tab>

  <Tab value="Rclone">
    | Field    | Required | Notes                                |
    | -------- | -------- | ------------------------------------ |
    | `remote` | yes      | Name of the configured rclone remote |
    | `path`   | yes      | Path inside that remote              |

    ```json
    {
    	"backend": "rclone",
    	"remote": "remote-name",
    	"path": "zerobyte/backups",
    	"isExistingRepository": true
    }
    ```

  </Tab>

  <Tab value="REST">
    | Field      | Required | Notes                                 |
    | ---------- | -------- | ------------------------------------- |
    | `url`      | yes      | Base REST server URL                  |
    | `username` | no       | Supports secret references            |
    | `password` | no       | Supports secret references            |
    | `path`     | no       | Optional path below the REST endpoint |

    ```json
    {
    	"backend": "rest",
    	"url": "https://rest-server.example.com",
    	"username": "env://REST_USERNAME",
    	"password": "file://rest_password",
    	"path": "zerobyte",
    	"isExistingRepository": true
    }
    ```

  </Tab>

  <Tab value="SFTP">
    | Field              | Required | Notes                                            |
    | ------------------ | -------- | ------------------------------------------------ |
    | `host`             | yes      | SFTP hostname                                    |
    | `port`             | no       | Defaults to `22`                                 |
    | `user`             | yes      | SSH username                                     |
    | `path`             | yes      | Remote repository path                           |
    | `privateKey`       | yes      | Private key contents. Supports secret references |
    | `skipHostKeyCheck` | no       | Defaults to `false`                              |
    | `knownHosts`       | no       | Contents of a known hosts file                   |

    ```json
    {
    	"backend": "sftp",
    	"host": "backup.example.com",
    	"port": 22,
    	"user": "backup",
    	"path": "/srv/restic",
    	"privateKey": "file://sftp_private_key",
    	"skipHostKeyCheck": false,
    	"knownHosts": "backup.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."
    }
    ```

  </Tab>
</Tabs>

## Volume Entry Reference

Each item in `volumes` uses this top-level shape:

```json
{
	"id": "volume-id",
	"organizationId": "org-id",
	"name": "Volume Name",
	"backend": "directory",
	"autoRemount": true,
	"delete": false,
	"config": {
		"backend": "directory",
		"path": "/data/volume"
	}
}
```

| Field            | Required | Notes                                                                              |
| ---------------- | -------- | ---------------------------------------------------------------------------------- |
| `id`             | yes      | Stable provisioning identifier inside the organization                             |
| `organizationId` | yes      | Must match an existing organization                                                |
| `name`           | yes      | Display name in Zerobyte                                                           |
| `backend`        | yes      | Must be one of `nfs`, `smb`, `directory`, `webdav`, `rclone`, `sftp`               |
| `autoRemount`    | no       | Defaults to `true`                                                                 |
| `delete`         | no       | Defaults to `false`                                                                |
| `config`         | yes      | Backend-specific object. `config.backend` must match the top-level `backend` value |

### Volume Backends

<Tabs items={["Directory", "NFS", "SMB/CIFS", "WebDAV", "SFTP", "Rclone"]}>
  <Tab value="Directory">
    | Field      | Required | Notes                              |
    | ---------- | -------- | ---------------------------------- |
    | `path`     | yes      | Path inside the Zerobyte container |
    | `readOnly` | no       | If provided, it must be `false`    |

    ```json
    {
    	"backend": "directory",
    	"path": "/data/documents"
    }
    ```

  </Tab>

  <Tab value="NFS">
    | Field        | Required | Notes                       |
    | ------------ | -------- | --------------------------- |
    | `server`     | yes      | NFS server hostname or IP   |
    | `exportPath` | yes      | Exported path on the server |
    | `port`       | no       | Defaults to `2049`          |
    | `version`    | yes      | Must be `3`, `4`, or `4.1`  |
    | `readOnly`   | no       | Mount the volume read-only  |

    ```json
    {
    	"backend": "nfs",
    	"server": "10.0.0.10",
    	"exportPath": "/exports/media",
    	"port": 2049,
    	"version": "4.1",
    	"readOnly": true
    }
    ```

  </Tab>

  <Tab value="SMB/CIFS">
    | Field      | Required | Notes                                                     |
    | ---------- | -------- | --------------------------------------------------------- |
    | `server`   | yes      | SMB server hostname or IP                                 |
    | `share`    | yes      | Share name                                                |
    | `username` | no       | Login username                                            |
    | `password` | no       | Supports secret references                                |
    | `guest`    | no       | Use guest authentication                                  |
    | `vers`     | no       | `1.0`, `2.0`, `2.1`, `3.0`, or `auto`. Defaults to `auto` |
    | `domain`   | no       | SMB domain or workgroup                                   |
    | `port`     | no       | Defaults to `445`                                         |
    | `readOnly` | no       | Mount the volume read-only                                |

    ```json
    {
    	"backend": "smb",
    	"server": "fileserver.local",
    	"share": "team",
    	"username": "backup-user",
    	"password": "env://SMB_PASSWORD",
    	"vers": "3.0",
    	"domain": "WORKGROUP",
    	"port": 445,
    	"readOnly": false
    }
    ```

  </Tab>

  <Tab value="WebDAV">
    | Field      | Required | Notes                           |
    | ---------- | -------- | ------------------------------- |
    | `server`   | yes      | WebDAV hostname, without scheme |
    | `path`     | yes      | Remote path                     |
    | `username` | no       | Login username                  |
    | `password` | no       | Supports secret references      |
    | `port`     | no       | Defaults to `80`                |
    | `readOnly` | no       | Mount the volume read-only      |
    | `ssl`      | no       | Enable HTTPS                    |

    ```json
    {
    	"backend": "webdav",
    	"server": "cloud.example.com",
    	"path": "/team-a",
    	"username": "team-a",
    	"password": "env://WEBDAV_PASSWORD",
    	"port": 443,
    	"ssl": true
    }
    ```

  </Tab>

  <Tab value="SFTP">
    | Field              | Required | Notes                                            |
    | ------------------ | -------- | ------------------------------------------------ |
    | `host`             | yes      | SFTP hostname                                    |
    | `port`             | no       | Defaults to `22`                                 |
    | `username`         | yes      | SSH username                                     |
    | `password`         | no       | Supports secret references                       |
    | `privateKey`       | no       | Private key contents. Supports secret references |
    | `path`             | yes      | Remote path                                      |
    | `readOnly`         | no       | Mount the volume read-only                       |
    | `skipHostKeyCheck` | no       | Defaults to `false`                              |
    | `knownHosts`       | no       | Contents of a known hosts file                   |

    ```json
    {
    	"backend": "sftp",
    	"host": "files.example.com",
    	"port": 22,
    	"username": "backup-user",
    	"privateKey": "file://volume_sftp_private_key",
    	"path": "/srv/data",
    	"readOnly": true,
    	"skipHostKeyCheck": false,
    	"knownHosts": "files.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."
    }
    ```

  </Tab>

  <Tab value="Rclone">
    | Field      | Required | Notes                                |
    | ---------- | -------- | ------------------------------------ |
    | `remote`   | yes      | Name of the configured rclone remote |
    | `path`     | yes      | Path inside that remote              |
    | `readOnly` | no       | Mount the volume read-only           |

    ```json
    {
    	"backend": "rclone",
    	"remote": "remote-name",
    	"path": "team-a",
    	"readOnly": true
    }
    ```

  </Tab>
</Tabs>

## Secret References

Supported secret references use one of these prefixes:

| Reference             | Resolves From                   | Example                        |
| --------------------- | ------------------------------- | ------------------------------ |
| `env://VARIABLE_NAME` | Container environment variables | `env://AWS_ACCESS_KEY_ID`      |
| `file://secret_name`  | `/run/secrets/secret_name`      | `file://aws_secret_access_key` |

<Callout type="info">
	`file://` references always resolve from `/run/secrets/` and must be a single filename, not a nested path.
</Callout>

Only specific fields are resolved as secret references. Other string fields are treated literally.

### Repository fields that support secret references

- shared fields: `customPassword`, `cacert`
- `s3`: `accessKeyId`, `secretAccessKey`
- `r2`: `accessKeyId`, `secretAccessKey`
- `gcs`: `credentialsJson`
- `azure`: `accountKey`
- `rest`: `username`, `password`
- `sftp`: `privateKey`

### Volume fields that support secret references

- `smb`: `password`
- `webdav`: `password`
- `sftp`: `password`, `privateKey`

Resolved secret values are encrypted before Zerobyte stores them in the database.

## Rotating Secrets

To rotate a provisioned secret:

1. Update the environment variable or secret file
2. Restart Zerobyte with `docker compose restart`

Zerobyte re-resolves supported secret references on each startup.

## Updating And Removing Managed Resources

- Keep the same `id` to update a managed resource in place
- Changing only `name` keeps the same underlying resource record
- Setting `"delete": true` removes the managed resource on the next startup sync

Deletion entries still need the normal entry shape today because the provisioning file is validated before Zerobyte applies the `delete` flag:

```json
{
	"id": "repo-to-remove",
	"organizationId": "your-organization-id",
	"name": "Repo to remove",
	"backend": "local",
	"delete": true,
	"config": {
		"backend": "local",
		"path": "/var/lib/zerobyte/repositories/old",
		"isExistingRepository": true
	}
}
```

## Troubleshooting

### `No matching discriminator`

This error usually means one of these is wrong:

- `backend` is not a valid value for that resource type
- `config.backend` does not match the top-level `backend`
- the `config` object does not match the required fields for that backend

### Secret reference errors

- `env://NAME` requires the environment variable to exist in the Zerobyte container
- `file://name` requires `/run/secrets/name` to exist
- Zerobyte stops syncing when it hits a provisioning error, so fix the first reported error and restart again

## Important Notes

<Callout type="warn">
	Each provisioned entry must reference an existing `organizationId`. Complete first-run setup before enabling
	provisioning so you have a valid organization ID.
</Callout>

- Changes to `provisioning.json` only apply on container restart
- Provisioned resources appear in the normal UI and are marked as managed
- Editing a provisioned resource in the UI is useful for testing, but the next provisioning sync can overwrite it

import { Tab, Tabs } from "fumadocs-ui/components/tabs";

---

### Apps Docs Content Docs Guides Rclone (`apps/docs/content/docs/guides/rclone.mdx`)

---
title: Rclone Integration
description: Use rclone with Zerobyte to connect to 40+ cloud storage providers for repositories and volumes
---

Rclone is a command-line tool that connects to over 40 cloud storage providers, including Google Drive, Dropbox, OneDrive, Box, pCloud, Mega, and many more. Zerobyte leverages rclone in two ways:

- **Repositories**, store your encrypted backups on any rclone-supported cloud provider
- **Volumes**, mount cloud storage as a data source so you can back it up

This guide covers installing rclone, connecting it to Zerobyte, and using it for both repositories and volumes.

## Setting up rclone

Before Zerobyte can use rclone, you need to install and configure it on your host machine.

<Steps>
<Step>

### Install rclone

Install rclone on your host system:

```bash
curl https://rclone.org/install.sh | sudo bash
```

Alternatively, visit [rclone.org/install](https://rclone.org/install/) for platform-specific packages.

</Step>

<Step>

### Configure a remote

Run the interactive configuration wizard:

```bash
rclone config
```

Follow the prompts to add a new remote. Each provider has its own setup flow, and rclone will walk you through authentication, API keys, or OAuth as needed.

</Step>

<Step>

### Verify your remotes

List all configured remotes:

```bash
rclone listremotes
```

</Step>

<Step>

### Test the connection

List the top-level directories on your remote to confirm everything works:

```bash
rclone lsd myremote:
```

Replace `myremote` with the name you chose during configuration.

</Step>
</Steps>

## Mounting rclone config into the container

Zerobyte needs access to your rclone configuration file to discover and use your remotes. Mount it as a read-only volume in your `compose.yaml`:

```yaml compose.yaml
volumes:
  - /etc/localtime:/etc/localtime:ro
  - /var/lib/zerobyte:/var/lib/zerobyte
  - ~/.config/rclone:/root/.config/rclone:ro
```

Then restart the container to pick up the change:

```bash
docker compose down && docker compose up -d
```

<Callout type="info">
For non-root container environments (e.g., TrueNAS), the config must be mounted to the correct user home directory. Set the `RCLONE_CONFIG_DIR` environment variable accordingly:

```yaml compose.yaml
environment:
  - RCLONE_CONFIG_DIR=/home/appuser/.config/rclone
volumes:
  - ~/.config/rclone:/home/appuser/.config/rclone:ro
```
</Callout>

## Using rclone for repositories

Rclone repositories let you store encrypted backups on any rclone-supported provider.

<Steps>
<Step>

### Open the Create Repository form

Navigate to **Repositories** in the sidebar and click **Create Repository**.

</Step>

<Step>

### Select rclone as the type

Choose **rclone** from the repository type dropdown.

</Step>

<Step>

### Choose your remote

Select your remote from the dropdown list. Zerobyte reads all configured remotes from the mounted rclone config.

</Step>

<Step>

### Specify a path

Enter a path within the remote where backups should be stored (e.g., `backups/zerobyte`).

</Step>

<Step>

### Create the repository

Click **Create and Initialize**. Zerobyte will initialize a new encrypted Restic repository on the remote.

</Step>
</Steps>

<Callout type="warn">
Consumer cloud services like Google Drive and Dropbox have API rate limits and are not designed for heavy I/O workloads. They work for personal or light-duty backups, but for production or critical data, use proper object storage such as S3, Backblaze B2, or Cloudflare R2.
</Callout>

## Using rclone for volumes

Rclone volumes mount cloud storage as a filesystem inside the container, allowing you to back up data stored in the cloud. This requires elevated container capabilities.

<Steps>
<Step>

### Ensure FUSE support

Your `compose.yaml` must include `SYS_ADMIN` and `/dev/fuse`:

```yaml compose.yaml
cap_add:
  - SYS_ADMIN
devices:
  - /dev/fuse:/dev/fuse
```

</Step>

<Step>

### Create a new volume

Navigate to **Volumes** in the sidebar and click **Create Volume**.

</Step>

<Step>

### Select rclone as the type

Choose **rclone** from the volume type dropdown.

</Step>

<Step>

### Choose the remote and path

Select your remote from the dropdown and specify the path within the remote you want to mount (e.g., `/` for the entire remote, or `/photos` for a subdirectory).

</Step>

<Step>

### Save and mount

Click **Create**. Zerobyte will mount the remote as a FUSE filesystem and make it available for backup jobs.

</Step>
</Steps>

<Callout type="warn">
Rclone volumes are **Linux-only**. Docker on macOS and Windows does not support FUSE mounts inside containers. If you are running Docker Desktop on macOS or Windows, rclone volumes will not work.
</Callout>

## SFTP remotes with SSH keys

If your rclone remote uses SFTP with `key_file` authentication, the path in the rclone config points to a location on the host filesystem, which is not accessible inside the container. There are three ways to solve this.

<Tabs items={["Mount SSH keys", "Embed key in config", "SSH agent"]}>
<Tab value="Mount SSH keys">

**Recommended.** Mount your SSH keys into the container so rclone can find them at the expected path:

```yaml compose.yaml
volumes:
  - ~/.ssh:/root/.ssh:ro
```

Make sure the `key_file` path in your rclone config matches the container path (e.g., `/root/.ssh/id_rsa`).

</Tab>

<Tab value="Embed key in config">

Instead of referencing a file with `key_file`, embed the private key directly in your rclone config using the `key_pem` field. Run `rclone config` on your host and choose the option to paste the key inline.

This avoids any file path issues since the key is stored directly in the rclone configuration file.

</Tab>

<Tab value="SSH agent">

Forward your host's SSH agent socket into the container:

```yaml compose.yaml
environment:
  - SSH_AUTH_SOCK=/ssh-agent
volumes:
  - ${SSH_AUTH_SOCK}:/ssh-agent
```

This allows rclone to authenticate using keys loaded into your host's SSH agent without exposing private key files.

</Tab>
</Tabs>

## Refreshing OAuth tokens

Rclone remotes that use OAuth (Google Drive, Dropbox, OneDrive, Box, and others) store authentication tokens in the rclone config file. These tokens expire periodically and need to be refreshed.

<Steps>
<Step>

### Re-authenticate on the host

Run the rclone config command on your host machine and follow the prompts to re-authorize the remote:

```bash
rclone config reconnect myremote:
```

</Step>

<Step>

### Restart the container

After refreshing the token, restart the Zerobyte container so it picks up the updated config:

```bash
docker compose restart
```

</Step>
</Steps>

<Callout type="info">
If you mount the rclone config as a read-only volume (`:ro`), the updated tokens on the host are immediately visible to the container after a restart. No need to copy files manually.
</Callout>

## Troubleshooting

### No remotes available

If the rclone remote dropdown is empty when creating a repository or volume:

- Verify the rclone config is mounted correctly. Check that `~/.config/rclone/rclone.conf` exists on the host.
- Confirm the mount path in `compose.yaml` matches the expected location inside the container (`/root/.config/rclone` by default, or the path set in `RCLONE_CONFIG_DIR`).
- Restart the container after updating the volume mount.

### Failed to create file system

This usually means rclone cannot authenticate with the remote:

- For OAuth remotes (Google Drive, Dropbox, OneDrive), the token may have expired. Re-authenticate with `rclone config reconnect myremote:` on the host and restart the container.
- For key-based remotes (S3, SFTP), verify that credentials in the rclone config are correct.
- Test the remote directly on the host: `rclone lsd myremote:` to isolate whether the issue is with rclone itself or with Zerobyte.

### FUSE mount errors for rclone volumes

If you see errors related to FUSE when creating rclone volumes:

- Ensure your `compose.yaml` includes `cap_add: [SYS_ADMIN]` and `devices: [/dev/fuse:/dev/fuse]`.
- Confirm you are running on a Linux host. FUSE mounts do not work with Docker on macOS or Windows.
- Check that `/dev/fuse` exists on the host: `ls -la /dev/fuse`.

<Callout type="info">
For more detailed troubleshooting steps, see the [Troubleshooting](/docs/troubleshooting) page.
</Callout>

import { Step, Steps } from "fumadocs-ui/components/steps";
import { Tab, Tabs } from "fumadocs-ui/components/tabs";

---

### Apps Docs Content Docs Guides Recovery Key And Repository Passwords (`apps/docs/content/docs/guides/recovery-key-and-repository-passwords.mdx`)

---
title: Recovery Keys
description: Understand the organization recovery key, imported repositories, and custom repository passwords
---

Zerobyte separates **account authentication** from **Restic repository encryption**. Your login password, your organization's recovery key, any imported repository password, and `APP_SECRET` all serve different purposes.

## The important secrets in Zerobyte

| Secret                          | What it is for                                                           | Where it applies           | Downloadable from the UI?                                       |
| ------------------------------- | ------------------------------------------------------------------------ | -------------------------- | --------------------------------------------------------------- |
| User password                   | Signing in to Zerobyte and confirming sensitive actions                  | Your user account          | No                                                              |
| Recovery key (`restic.pass`)    | Opening repositories that use the organization's default Restic password | The active organization    | Yes, for org owners and admins after password re-authentication |
| Custom repository password      | Opening an imported repository that uses a different Restic password     | One imported repository    | No separate download flow                                       |
| `APP_SECRET`                    | Encrypting secrets that Zerobyte stores in the database                  | The application deployment | No                                                              |
| Configuration export passphrase | Decrypting one `.zbex` configuration export                              | One exported file          | Chosen or generated when exporting                              |

## What the recovery key actually is

The downloaded `restic.pass` file contains the **Restic password for the active organization**.

That organization-level password is what Zerobyte uses when it:

- Initializes a brand-new repository from the UI
- Accesses a repository that relies on the organization's default password
- Runs normal backup, restore, snapshot, doctor, and stats operations for those repositories

<Callout type="warn">
	Store the recovery key outside Zerobyte in a password manager, secret vault, or other encrypted storage. If you lose
	both server access and the correct Restic password, your backups are not recoverable.
</Callout>

## First-run behavior for local users

During first-run onboarding, the first local admin is created with a flag indicating they have **not** downloaded the recovery key yet.

Until that flag is cleared, Zerobyte redirects the user to `/download-recovery-key` instead of the normal app pages.

When the user downloads the file successfully:

- Zerobyte verifies the user's current password
- The response returns the plaintext `restic.pass` file
- Zerobyte marks that user as having downloaded the recovery key

After that, future logins go straight to the app.

## SSO behavior

SSO-created users are marked as having already downloaded the recovery key, so they are **not** forced through the recovery-key download screen during first login.

That means SSO rollouts should treat recovery key storage as an organization-admin responsibility. Ordinary SSO users are not individually prompted to save the key.

## Who can download the recovery key

The current download endpoint requires all of the following:

- You are signed in
- Your active organization role is `admin` or `owner`
- You re-enter your current account password

The downloaded filename is `restic.pass`.

## New repositories vs imported repositories

| Repository scenario                                                          | Password Zerobyte uses                 | What to choose in the UI                                       |
| ---------------------------------------------------------------------------- | -------------------------------------- | -------------------------------------------------------------- |
| New repository created in Zerobyte                                           | The active organization's recovery key | No extra password choice is shown                              |
| Existing repository that already uses the active organization's recovery key | The active organization's recovery key | `Import existing repository` + `Use the existing recovery key` |
| Existing repository created elsewhere with a different Restic password       | A per-repository custom password       | `Import existing repository` + `Enter password manually`       |

### Important limitation

The custom password option only exists when **Import existing repository** is enabled. Brand-new repositories created by Zerobyte always use the active organization's recovery key.

## Configuration exports

Organization owners and admins can download a `.zbex` file containing the organization's repositories, volumes, schedules, notification destinations, and recovery key. Zerobyte decrypts only those exported values in memory and encrypts the complete file with a dedicated export passphrase.

The export passphrase is separate from your account password, recovery key, and `APP_SECRET`. Zerobyte does not store it and cannot recover it.

To restore the configuration during onboarding on another instance, you need:

- The `.zbex` file
- Its export passphrase

The source instance's `APP_SECRET` is not exported or required. After import, the destination instance encrypts the imported credentials using its own `APP_SECRET`.

<Callout type="warn">
	Store the export and its passphrase separately. Anyone with both can decrypt the credentials and recovery key
	contained in the export.
</Callout>

## How Zerobyte decides which password to use

Zerobyte's runtime password selection is simple:

1. If the repository is imported **and** has a stored custom password, use that custom password.
2. Otherwise, use the active organization's recovery key.

This has an important consequence: downloading the organization recovery key will **not** help with an imported repository that is configured with its own custom password.

## Choosing the right import option

Choose **Use the existing recovery key** only when the repository already uses the same Restic password as the active organization.

Typical examples:

- A repository previously created by the same Zerobyte organization
- A migrated environment where the organization password was preserved

Choose **Enter password manually** when the repository was initialized outside this organization, for example:

- A repository created with the Restic CLI
- A repository created by another Zerobyte organization
- A repository created by another tool that happened to use Restic

## Practical guidance

- Keep a secure inventory of which imported repositories use custom passwords.
- Do not assume all repositories in the same Zerobyte instance share the same password model.
- If you manage multiple organizations, remember that the downloaded recovery key is for the **active** organization.
- Test restores using the same password model the repository actually uses.
- Treat `APP_SECRET` and the recovery key as separate operational secrets. Losing one is not the same as losing the other.
- Keep each configuration export passphrase with the same care as the export itself; neither can be recovered from the other.

## Related docs

- [Repositories](/docs/concepts/repositories)
- [Repository maintenance and status](/docs/guides/repository-maintenance)

import { Callout } from "fumadocs-ui/components/callout";

---

### Apps Docs Content Docs Guides Remote Agents (`apps/docs/content/docs/guides/remote-agents.mdx`)

---
title: Remote agents
description: Back up files from another Linux machine with Zerobyte.
---

import { Callout } from "fumadocs-ui/components/callout";

<Callout type="warn" title="Unreleased feature">
	Remote agents are still in development and aren't available in a stable Zerobyte release. This guide describes the
	upcoming feature. Installation commands will work once the installer and agent binaries are published.
</Callout>

A remote agent lets Zerobyte back up files on another machine without mounting its folders on the Zerobyte server. The agent runs Restic on that machine and sends backups directly to your repository. You manage the sources and backup schedules from Zerobyte as usual.

The agent connects to Zerobyte, so you don't need to open an inbound port on the remote machine. It must be able to reach both your Zerobyte server and the backup repository.

## Before you start

On the machine you want to back up, you'll need:

- Linux on x64 or ARM64, with glibc and systemd.
- [Restic 0.18.0 or newer](https://restic.readthedocs.io/en/stable/020_installation.html), installed on the service's PATH, such as `/usr/bin/restic` or `/usr/local/bin/restic`.
- Sudo access to install and manage the agent service.

The agent is a standalone executable. You don't need to install Bun. The installer doesn't support Alpine Linux, macOS, or Windows.

Your Zerobyte server should have an HTTPS address that the remote machine can reach. Remote agents are available in the server version of Zerobyte, not the desktop app.

## Enable remote agents

Add the following environment variable to your Zerobyte service and restart it:

```yaml
environment:
  ENABLE_REMOTE_AGENTS: "true"
```

Set `BASE_URL` to the HTTPS address you use to access Zerobyte. If a reverse proxy handles HTTPS, also set `TRUST_PROXY=true` and enable WebSocket forwarding. See the [reverse proxy guide](/docs/guides/reverse-proxy) for configuration examples.

## Connect a machine

1. Open **Settings**, choose your organization, and find **Machines**.
2. Click **Connect machine**, give the machine a name, and click **Continue**.
3. Choose **Linux installer**, copy the command, and run it in a terminal on that machine.
4. Keep the setup page open. It advances to **Connection successful** once the agent is connected and ready. Review its allowed locations, then create a source.

The command downloads the agent, checks its checksum and Restic installation, then installs a systemd service. The agent starts immediately and starts again after a reboot. Once it connects, its status in Zerobyte changes to **Online**.

The connection code in the command can be used once and expires after 15 minutes. If it expires, get a new command from the machine's **Actions** menu.

## Run with Docker

The agent image is published at `ghcr.io/nicotsx/zerobyte-agent` for Linux AMD64 and ARM64. Use the same version tag as your Zerobyte server, such as `vX.Y.Z`. Stable releases also have `latest`, major, and minor tags; development builds use `nightly` or `sha-<commit>` tags.

The image contains the standalone agent, Restic, CA certificates, and an SSH client for SFTP repositories. It runs in the foreground without systemd. Mount the directories you want to back up from the host; network filesystems should be mounted on the host first.

In **Settings → Machines**, connect a machine and choose the **Docker** tab. Copy its prefilled configuration into `compose.yaml` on the machine you want to back up. It includes the controller address and a single-use connection code:

```yaml
services:
  agent:
    image: ghcr.io/nicotsx/zerobyte-agent:vX.Y.Z
    hostname: remote-agent
    restart: unless-stopped
    environment:
      ZEROBYTE_CONTROLLER_URL: "https://zerobyte.example.com"
      ZEROBYTE_AGENT_ENROLLMENT_CODE: "YOUR_CONNECTION_CODE"
      ZEROBYTE_AGENT_ROOTS: >-
        [{"id":"files-1","label":"Files","path":"/data","allowBackup":true}]
    volumes:
      - agent-config:/var/lib/zerobyte-agent
      - agent-cache:/var/cache/zerobyte-agent
      - /path/on/host:/data:ro

volumes:
  agent-config:
  agent-cache:
```

Replace `/path/on/host` with the directory you want to back up, then start the service before the code expires:

```bash
docker compose up -d
```

The agent enrolls automatically on first startup and saves its machine credential in the configuration volume. Restarts reuse that credential without consuming the connection code again. Once the machine is **Online**, you can remove `ZEROBYTE_AGENT_ENROLLMENT_CODE` from the YAML. Keep the configuration volume across container upgrades. The cache volume avoids downloading repository metadata again after recreation. No published ports or privileged mode are needed.

Manage allowed locations through `ZEROBYTE_AGENT_ROOTS` in Compose, then recreate the container. Use container paths, and mount each path you share:

```yaml
environment:
  ZEROBYTE_AGENT_ROOTS: '[{"id":"files-1","label":"Files","path":"/data","allowBackup":true}]'
```

Keep existing root IDs when changing paths so sources continue to reference the same location. Use `docker compose logs -f agent` for logs. To update, change the image tag, run `docker compose pull`, then `docker compose up -d` between backup runs.

To reconnect an existing container, use that machine's **Actions → Get connection command**, confirm the reconnect warning, and choose **Docker**. Replace the enrollment code in your existing YAML and run `docker compose up -d --force-recreate`. Preserve your configuration volume and allowed-location settings; the fresh code reconnects the same machine.

## Allow insecure HTTP connections

HTTPS is required by default. If you deliberately use an HTTP controller, set `ZEROBYTE_AGENT_ALLOW_INSECURE_HTTP=true` on **both the Zerobyte server and the remote agent**, then restart both. Set the server's `BASE_URL` to its reachable HTTP address.

For Docker agents, use:

```yaml
environment:
  ZEROBYTE_CONTROLLER_URL: "http://zerobyte.example.com"
  ZEROBYTE_AGENT_ALLOW_INSECURE_HTTP: "true"
  ZEROBYTE_AGENT_ENROLLMENT_CODE: "YOUR_CONNECTION_CODE"
```

The option applies to initial enrollment, reconnection, and agents using `ZEROBYTE_AGENT_TOKEN` directly. With a token, `ZEROBYTE_CONTROLLER_URL` accepts an HTTP/HTTPS controller address or a WS/WSS connection URL. The option defaults to `false`; only the exact value `true` enables it. It does not disable HTTPS certificate verification.

For a Linux service, set the variable when running the installer (after `sudo env`) to allow HTTP enrollment. The saved controller address is reused on service restarts. The existing `--allow-insecure` CLI option also allows HTTP enrollment.

HTTP sends machine credentials and controller traffic, including repository credentials, without TLS encryption. **Settings → Machines** shows an **Insecure HTTP** badge and a warning on connected machines using HTTP. Offline machines retain a warning about their last connection. Use HTTPS to protect the connection.

## Choose folders to back up

With the Linux installer, a newly connected agent doesn't share any folders. On the remote machine, run:

```bash
sudo zerobyte-agent folders add
```

Use the folder picker to select the directories you want Zerobyte to access. You can choose several folders and run the command again later to add more. The agent restarts after you add folders, so do this between backup runs.

The selected folders appear as **Allowed locations** on the machine's card in Zerobyte. You can also list them from the terminal:

```bash
sudo zerobyte-agent folders list
```

Zerobyte can browse and back up files inside these locations. Files outside them aren't available through the agent.

## Create a backup

In **Sources**, create a source and choose **Another machine**. Select your connected machine and an allowed location. You can back up the whole location or browse to a folder inside it.

Save the source, then create a backup job using it. Choose a repository that the remote machine can reach and set your schedule. Local and rclone repositories aren't available for remote sources because they rely on paths or configuration on the Zerobyte server.

Restores currently run on the Zerobyte server. To recover files from a remote backup, restore them to a custom location on the server or download them, then copy them back to the remote machine.

## Check the agent

The machine's card shows its status, last contact, hostname, platform, and allowed locations. Agents that report version information also show their agent and Restic versions.

On the remote machine, use these commands to check the service and follow its logs:

```bash
sudo zerobyte-agent status
sudo zerobyte-agent logs --follow
```

Press Ctrl+C to stop following the logs. The agent keeps running in the background. You can also use `start`, `stop`, and `restart` to manage the service.

If the connection drops, the agent retries automatically. The delay increases between failed attempts, up to roughly 30 seconds.

## Update the agent

To install the latest stable agent release, run the installer again without a connection code:

```bash
curl -fsSL https://zerobyte.app/install.sh | sudo bash
```

To install a specific release, set `ZEROBYTE_AGENT_VERSION`:

```bash
curl -fsSL https://zerobyte.app/install.sh | sudo env ZEROBYTE_AGENT_VERSION=vX.Y.Z bash
```

Updates preserve the machine's connection and allowed locations, then restart the service. Run them between backup runs. Agents don't update automatically.

If you've downloaded a new agent executable yourself, run its `install` command without a connection code to update the existing installation. A connection code is only needed for first-time setup or reconnection.

## Reconnect or revoke a machine

To replace a machine's credential, open its **Actions** menu in Zerobyte and choose **Get connection command**. Run the new command on the same machine. The agent detects its saved identity and reconnects automatically, preserving the allowed locations and existing sources.

Generating that command disconnects the current agent and invalidates its old credential. Do this between backup runs, and keep the saved configuration at `/var/lib/zerobyte-agent/agent.json` so the new command can reconnect the existing installation.

Choose **Revoke connection** to prevent a machine from connecting. Its sources and backup schedules remain saved. To use the machine again, reconnect it from the same machine card.

## Troubleshooting

### The installer says the machine is already enrolled

The machine already has a saved identity. To update it, run the installer again without a connection code, as described in [Update the agent](#update-the-agent).

If you need to replace the credential, choose **Get connection command** from the existing machine's **Actions** menu and run its new command. Keep `agent.json`; deleting it also loses the saved folder selection.

### Enrollment fails with “TLS required”

Check that the controller URL uses HTTPS. When a trusted proxy terminates HTTPS and forwards to Zerobyte over HTTP, set `TRUST_PROXY=true` on Zerobyte and make sure the proxy forwards the original secure protocol.

### The machine stays offline after installation

Check `sudo zerobyte-agent logs --follow`. Your proxy must allow WebSocket upgrades to `/api/v1/agents/connect` and preserve the `Authorization` header. With `TRUST_PROXY=true`, Zerobyte accepts `X-Forwarded-Proto: https` and `X-Forwarded-Proto: wss` for secure connections.

If the logs report a Restic or configuration error, fix it and run `sudo zerobyte-agent restart`. These errors stop the service until you restart it.

### The machine is online but has no allowed locations

Run `sudo zerobyte-agent folders add` on the remote machine. Registering a machine doesn't share its filesystem automatically.

---

### Apps Docs Content Docs Guides Repository Maintenance (`apps/docs/content/docs/guides/repository-maintenance.mdx`)

---
title: Repository Maintenance
description: Interpret repository health states and use Doctor, Unlock, and stats refresh correctly
---

The repository details page exposes a small set of maintenance tools, but they do different jobs. This guide explains what each status means and what the built-in actions actually do.

## Repository status values

| Status | What it means in Zerobyte | What to do next |
| --- | --- | --- |
| `healthy` | The last stored health verdict finished without errors | Keep monitoring and refresh stats when you want updated size metrics |
| `error` | The last health or doctor run recorded an error | Review **Last Error** and the **Doctor Report**, fix the underlying issue, then rerun maintenance if needed |
| `unknown` | Zerobyte does not currently have a fresh health verdict, or repository settings were changed and the old verdict was cleared | Expect this after some config changes; verify access and consider running Doctor |
| `doctor` | A Doctor run is in progress | Wait for it to finish or cancel it during a maintenance window |
| `cancelled` | A Doctor run was aborted before completion | Review any partial Doctor output and rerun Doctor if you still need validation |

Repository status is stored metadata inside Zerobyte. It is not a continuous live probe.

## What each action really does

| Action | What it does | What it does **not** do |
| --- | --- | --- |
| `Run doctor` | Runs `unlock`, then `check`, then `repair-index` if restic says it is needed. Saves step output into the Doctor Report. | It does **not** run a full `check --read-data` scrub, it does **not** prune unused data, and it does **not** refresh the stats card. |
| `Unlock` | Runs `restic unlock` to clear stale repository locks. | It does **not** validate repository integrity and it does **not** change the stored health status by itself. |
| Stats refresh button | Runs `restic stats --mode raw-data` and updates stored size and snapshot metrics. | It does **not** change repository health status, **Last Checked**, or the Doctor Report. |

## Run doctor in detail

Doctor is the deepest repository-maintenance action currently exposed in the normal UI.

During a Doctor run, Zerobyte:

1. Sets the repository status to `doctor`
2. Acquires an exclusive repository lock inside the app
3. Runs an unlock step
4. Runs `restic check`
5. Runs `restic repair-index` only if the check output suggests that repair
6. Saves the step-by-step result and updates the final status to `healthy`, `error`, or `cancelled`

Because Doctor holds an exclusive repository lock, it can block other repository operations such as backups, restores, and snapshot mutations while it runs.

<Callout type="warn">
  Doctor is helpful, but it is lighter than a full offline integrity scrub. Today it does not read every stored data pack and it does not prune repository data. Treat `healthy` as a useful application-level signal, not as proof that every blob was recently re-read.
</Callout>

## When to use Run doctor

- The repository status is `error`
- **Last Error** suggests metadata or index problems
- Operations keep failing after a crash or interrupted run
- You changed repository settings and want a fresh maintenance pass
- You want a stored Doctor Report before or after backend maintenance work

## When to use Unlock

Use **Unlock** when the problem is specifically stale repository locks, for example after:

- A crashed backup or restore
- A forced container restart
- A cancelled or interrupted maintenance run
- A Restic message that says the repository is locked

If the problem is not a lock issue, Unlock will not fix it.

<Callout type="warn">
  Only run Unlock when you are certain no real backup, restore, or maintenance operation is still using that repository.
</Callout>

## What stats refresh actually recalculates

The refresh button in **Compression Statistics** recalculates repository metrics using `restic stats --mode raw-data`, including:

- Stored size
- Uncompressed size
- Compression ratio
- Compression progress
- Compression space saving
- Snapshot count

This is useful when:

- The stats card is still empty after connecting an existing repository
- You deleted snapshots and want updated numbers
- Retention cleanup changed repository usage
- You want fresh size numbers without running Doctor

## Reading the Doctor Report

The **Doctor Report** stores each maintenance step with its output and any error text. In current builds, the steps you will typically see are:

- `unlock`
- `check`
- `repair_index` when suggested by restic

If a Doctor run is cancelled, the report can still contain partial step output from the work that completed before cancellation.

## Practical workflows

### Repository is locked after a crash

1. Use **Unlock**.
2. Retry the blocked backup or restore.
3. If problems continue, run **Doctor**.

### Status is `unknown`

1. Check whether you recently edited repository settings.
2. Use the stats refresh button if you only need updated size numbers.
3. Run **Doctor** if you want a fresh maintenance verdict.

### Status is `error`

1. Read **Last Error**.
2. Expand the **Doctor Report** and identify the failing step.
3. Fix the real problem first, such as credentials, backend reachability, or repository corruption.
4. Run **Doctor** again after the fix.

## Related docs

- [Repositories](/docs/concepts/repositories)
- [Recovery keys and repository passwords](/docs/guides/recovery-key-and-repository-passwords)

import { Callout } from "fumadocs-ui/components/callout";

---

### Apps Docs Content Docs Guides Restoring (`apps/docs/content/docs/guides/restoring.mdx`)

---
title: Restoring Data
description: Restore files and folders from snapshots, choose the right destination, and avoid common restore pitfalls
---

Zerobyte lets you recover data in two ways:

- **Restore**, writes files back to a filesystem path
- **Download**, retrieves the selected contents without restoring them directly into a mounted destination

This guide covers when to use each option, how the restore form works, and what to do when a destination rejects ownership or permission changes.

## Before you restore

Before starting a restore, check these basics:

- the repository is reachable and unlocked
- the target path is writable
- the destination has enough free space

If you are restoring into a path that is bind-mounted into the container, make sure it is **not** mounted read-only. Read-only mounts can be backed up, but they cannot accept restores back into the original location.

<Callout type="warn">
If you are not fully sure about the target, restore to a custom staging path first. It is safer, easier to verify, and reduces the chance of overwriting live data.
</Callout>

## Restore vs Download

Use **Restore** when you want Zerobyte to write files back onto a filesystem path.

Use **Download** when you want to recover the contents without restoring them directly into the current mounted destination.

As a rule of thumb:

- use **Restore** for normal recovery back to a server path
- use **Download** for one-off recovery to your workstation, or when a mounted destination keeps failing on permissions or metadata

In the current UI, **Download** is available when you select a single item, or when you leave the selection empty and download the snapshot contents directly.

## Standard restore workflow

<Steps>
<Step>

### Open the backup job

Go to **Backups** and open the job that contains the data you need.

</Step>

<Step>

### Choose the snapshot

Select the snapshot from the time period you want to recover.

</Step>

<Step>

### Browse and select data

Use the file browser to inspect the snapshot and optionally select specific files or folders.

If you leave the selection empty, Zerobyte restores the full snapshot scope that is currently being viewed.

</Step>

<Step>

### Choose the destination

Pick either:

- **Original location**, to put the data back where it came from
- **Custom location**, to restore into another path

</Step>

<Step>

### Review overwrite behavior

Choose how Zerobyte should handle files that already exist at the target.

</Step>

<Step>

### Start the restore

Click **Restore** and wait for the progress indicator to finish. When it completes, verify that the recovered files are present and usable.

</Step>
</Steps>

## Choosing the restore location

### Original location

Original-location restore is convenient when you want to put the snapshot back exactly where it came from.

Use it when:

- the original path still exists
- the path is writable
- you are intentionally restoring back in place

Be careful with it because it targets live paths.

### Custom location

Custom-location restore writes the recovered data to another path that you choose.

This is the safer default when:

- you want to inspect the recovered files first
- you are restoring onto a different server or container
- the original location is no longer correct
- you expect permission or metadata problems on the final destination

<Callout type="info">
If the snapshot was created from non-POSIX paths, or from source paths that do not match the current Zerobyte server or linked volume, original-location restore is unavailable. Restore to a custom location instead.
</Callout>

## Overwrite modes

Zerobyte exposes Restic's overwrite behavior in the restore form.

### Always overwrite

Replace existing files with the snapshot version. This is the most direct in-place restore mode.

### Only if content changed

Overwrite existing files only when their content differs from the snapshot. This avoids replacing identical files unnecessarily.

### Only if snapshot is newer

Overwrite existing files only when the snapshot version has a newer modification time.

### Never overwrite

Restore only missing files. Existing files are left in place.

## Advanced option: excluding extended attributes

The restore form includes an **Exclude extended attributes** field under **Advanced options**.

Use it only when the destination accepts file contents but rejects specific xattrs. Common examples include:

- `security.*`
- `system.*`
- ACL-related attributes

This is useful for narrowly targeted compatibility problems. It does **not** fix ownership or mode failures like `lchown` or `chmod`.

## Why some restores fail on permissions or ownership

If you see errors like these:

```text
lchown /path/to/file: operation not permitted
lchown /path/to/file: permission denied
chmod /path/to/file: operation not permitted
failed to restore timestamp
```

the data copy often succeeded. The failure usually happens afterward, when Restic tries to re-apply the original metadata from the snapshot.

Restic restores more than file contents. It also restores metadata such as:

- owner and group
- file mode
- timestamps
- extended attributes and ACL-related metadata

That works well on a normal local Linux filesystem. It gets more complicated when the target is a mounted or translated filesystem such as:

- SFTP volumes mounted through SSHFS
- SMB or CIFS shares
- WebDAV volumes
- rclone-mounted cloud storage
- Windows systems accessed through SFTP
- NAS or appliance-managed shares that do not allow arbitrary ownership changes

These destinations may let Zerobyte write file contents while still rejecting `chown`, `chmod`, timestamp, or xattr updates.

<Callout type="info">
Mounted destinations can expose translated metadata. In Zerobyte today, WebDAV and SFTP mounts still pass the container process `uid` and `gid` to the mount command, and SMB mounts still depend on what the Linux CIFS client can represent from the remote server. A backup taken from any mounted view records what Linux can see there, which may not be the remote system's full ACL model.

For the practical implications and backend-by-backend tradeoffs, see [Mounted Shares and Permissions](/docs/guides/mounted-shares-and-acls).
</Callout>

## What Zerobyte can and cannot change

Zerobyte can:

- restore to another target path
- restore selected files or folders
- let you download data instead of restoring it directly
- exclude specific extended attributes during restore

Zerobyte cannot currently tell Restic to restore file contents while completely skipping owner, group, mode, and timestamp restoration. Restic supports xattr filtering, but it does not provide a general "ignore metadata" restore mode.

That means a restore directly into a metadata-rejecting mount cannot be made fully reliable without using a different destination or a second manual copy step.

## Reliable workarounds

### Restore to a different place first

This is the most reliable workaround.

Restore to a local Linux path or another writable POSIX filesystem first, verify the recovered files, then move or copy them to the final destination manually.

This works because:

- Restic finishes on a filesystem that accepts metadata updates
- the later copy step only has to match what the final destination actually supports

### Use Download for small recoveries

If you only need one file or one folder quickly, use **Download** instead of **Restore**.

This avoids asking the mounted destination to accept snapshot ownership and permission metadata.

### Exclude failing xattrs when that is the only problem

If the failure is limited to extended attributes, exclude the failing xattr under **Advanced options** and retry.

Do not expect this to solve `lchown` or `chmod` failures.

### Prefer host mounts or native paths

If you control the Docker host, a more reliable pattern is:

1. mount the remote share on the host
2. bind-mount that host path into the Zerobyte container
3. restore to that bind-mounted path or to another local staging path first

This often behaves better than restoring straight through a FUSE-based mount created inside the container.

### Use a filesystem that supports POSIX metadata when exact restore fidelity matters

If you need the restored data to keep Unix owner, group, and mode information exactly, restore to a destination that supports those semantics end-to-end.

That is usually a local Linux filesystem or a properly configured NFS share, not a translated or appliance-managed mount.

## Practical recommendations

- Test restores regularly, not just backups
- Prefer a custom restore path for routine verification
- Keep at least one fast local repository for quick recovery
- Use **Download** for ad-hoc recovery to your own machine
- Verify recovered files before deleting or overwriting the originals

## Related guides

- [Quick Start](/docs/quickstart)
- [3-2-1 Backup Strategy](/docs/guides/3-2-1-backup-strategy)
- [Recovery keys and repository passwords](/docs/guides/recovery-key-and-repository-passwords)
- [Troubleshooting](/docs/troubleshooting)

import { Callout } from "fumadocs-ui/components/callout";
import { Step, Steps } from "fumadocs-ui/components/steps";

---

### Apps Docs Content Docs Guides Reverse Proxy (`apps/docs/content/docs/guides/reverse-proxy.mdx`)

---
title: Reverse Proxy
description: Run Zerobyte behind Nginx, Caddy, or Traefik with HTTPS
---

When running Zerobyte behind a reverse proxy, you need to configure the `BASE_URL` environment variable with your HTTPS domain and ensure your proxy passes the correct headers.

## Prerequisites

Set the following environment variables in your `compose.yaml`:

```yaml compose.yaml
environment:
  - BASE_URL=https://zerobyte.example.com
  - TRUST_PROXY=true
```

`TRUST_PROXY=true` lets authentication use the public host and protocol from `X-Forwarded-Host` and `X-Forwarded-Proto`. Set it when your HTTPS proxy forwards requests to Zerobyte over HTTP. Only enable it when the proxy controls those headers and clients cannot reach Zerobyte directly.

Restart the container after making changes:

```bash
docker compose down && docker compose up -d
```

## How BASE_URL affects cookie security

Zerobyte uses `BASE_URL` to determine whether authentication cookies should be marked as `Secure`. This directly affects how login sessions work:

| BASE_URL value | Cookie behavior |
|---|---|
| `http://` or IP address (e.g., `http://192.168.1.100:4096`) | Secure cookies **disabled**, cookies sent over HTTP. Suitable for local network access. |
| `https://` with domain (e.g., `https://zerobyte.example.com`) | Secure cookies **enabled**, cookies only sent over HTTPS. Required for production. |

<Callout type="warn">
If `BASE_URL` is set to an `https://` address, browsers will **only** send authentication cookies over HTTPS connections. Accessing Zerobyte over plain HTTP will fail with login loops or session errors. Make sure your reverse proxy terminates TLS before forwarding to Zerobyte.
</Callout>

## Proxy configurations

<Tabs items={["Nginx", "Caddy", "Traefik"]}>
<Tab value="Nginx">

### Nginx

Create a server block for Zerobyte. This configuration handles TLS termination and forwards requests to the container:

```nginx nginx.conf
server {
    listen 443 ssl http2;
    server_name zerobyte.example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://localhost:4096;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

Reload Nginx after saving:

```bash
sudo nginx -t && sudo systemctl reload nginx
```

</Tab>

<Tab value="Caddy">

### Caddy

Caddy automatically provisions and renews TLS certificates from Let's Encrypt. Add this to your `Caddyfile`:

```text Caddyfile
zerobyte.example.com {
    reverse_proxy localhost:4096
}
```

Reload Caddy after saving:

```bash
sudo systemctl reload caddy
```

Caddy automatically handles `X-Forwarded-For`, `X-Forwarded-Proto`, and TLS, with no extra configuration needed.

</Tab>

<Tab value="Traefik">

### Traefik

If you run Traefik as your reverse proxy, add labels to the Zerobyte service in your `compose.yaml`:

```yaml compose.yaml
services:
  zerobyte:
    image: ghcr.io/nicotsx/zerobyte:v0.43
    container_name: zerobyte
    restart: unless-stopped
    environment:
      - BASE_URL=https://zerobyte.example.com
      - APP_SECRET=your-secret-here
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.zerobyte.rule=Host(`zerobyte.example.com`)"
      - "traefik.http.routers.zerobyte.entrypoints=websecure"
      - "traefik.http.routers.zerobyte.tls.certresolver=letsencrypt"
      - "traefik.http.services.zerobyte.loadbalancer.server.port=4096"
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/lib/zerobyte:/var/lib/zerobyte
```

Make sure your Traefik instance is configured with a `websecure` entrypoint and a `letsencrypt` certificate resolver.

</Tab>
</Tabs>

## Binding to localhost

If you are using a reverse proxy on the same machine, bind the Zerobyte port to `127.0.0.1` so it is not directly accessible from the network:

```yaml compose.yaml
ports:
  - "127.0.0.1:4096:4096"
```

This ensures all traffic goes through your reverse proxy, which handles TLS and authentication headers.

## Security considerations

<Callout type="warn">
**Exposing Zerobyte to the internet requires care.** Even behind a reverse proxy with HTTPS, make sure you:

- Bind the container port to localhost only (`127.0.0.1:4096:4096`) so it cannot be accessed directly.
- Use a strong `APP_SECRET` (generated with `openssl rand -hex 32`).
- Keep Zerobyte updated to the latest version.
- Consider placing Zerobyte behind a secure tunnel (Cloudflare Tunnel, Tailscale, WireGuard) for an additional layer of protection.
</Callout>

### A note on TRUSTED_ORIGINS

The `TRUSTED_ORIGINS` environment variable allows you to add additional CORS origins for cross-origin requests. It does **not** make HTTP work when `BASE_URL` is set to HTTPS. If your `BASE_URL` uses `https://`, all access must go through HTTPS regardless of what is listed in `TRUSTED_ORIGINS`.

```yaml
# TRUSTED_ORIGINS only adds allowed CORS origins, it does NOT downgrade HTTPS to HTTP
environment:
  - BASE_URL=https://zerobyte.example.com
  - TRUSTED_ORIGINS=https://other-app.example.com
```

## Verifying your setup

After configuring your reverse proxy:

<Steps>
<Step>

### Test HTTPS access

Open `https://zerobyte.example.com` in your browser. You should see the Zerobyte login page with a valid TLS certificate.

</Step>

<Step>

### Check headers

Verify that your proxy is forwarding the correct headers. In the Zerobyte container logs, you should see requests coming from your proxy:

```bash
docker compose logs -f zerobyte
```

</Step>

<Step>

### Confirm login works

Log in with your admin credentials. If login fails or loops, double-check that `BASE_URL` matches the URL you are accessing and that cookies are being set correctly over HTTPS.

</Step>
</Steps>

import { Step, Steps } from "fumadocs-ui/components/steps";
import { Tab, Tabs } from "fumadocs-ui/components/tabs";

---

### Apps Docs Content Docs Guides Tailscale (`apps/docs/content/docs/guides/tailscale.mdx`)

---
title: Tailscale Sidecar
description: Run Zerobyte behind a Tailscale VPN for secure remote access
---

Run Zerobyte behind a Tailscale sidecar container so the web UI is accessible over your private tailnet without exposing ports to the public internet.

## What is Tailscale?

[Tailscale](https://tailscale.com/) is a mesh VPN built on WireGuard. It connects devices and containers into a private network ("tailnet") without opening inbound ports on your router.

In this setup, Tailscale acts as a secure access layer in front of Zerobyte:

- You reach Zerobyte using the node's tailnet IP or MagicDNS name
- Access can be restricted using Tailscale ACLs and tags
- No port forwarding or public exposure required

## Prerequisites

- Docker and Docker Compose
- A [Tailscale account](https://tailscale.com/) and an [auth key](https://login.tailscale.com/admin/settings/keys)

## Setup

This example uses a sidecar networking pattern where Zerobyte shares the Tailscale container's network namespace.

<Steps>
<Step>

### Create compose.yaml

```yaml compose.yaml
services:
  tailscale:
    image: tailscale/tailscale:stable
    container_name: zerobyte-tailscale
    hostname: ${TS_HOSTNAME:-zerobyte}
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    environment:
      - TS_AUTHKEY=${TS_AUTHKEY}
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_USERSPACE=${TS_USERSPACE:-false}
      - TS_EXTRA_ARGS=${TS_EXTRA_ARGS:-}
    volumes:
      - /var/lib/tailscale:/var/lib/tailscale
    # Remove this section to make Zerobyte only accessible via Tailscale
    ports:
      - "4096:4096"

  zerobyte:
    image: ghcr.io/nicotsx/zerobyte:v0.43
    container_name: zerobyte
    restart: unless-stopped
    # Uncomment if you need remote mounts (NFS/SMB/WebDAV):
    # cap_add:
    #   - SYS_ADMIN
    # devices:
    #   - /dev/fuse:/dev/fuse
    network_mode: service:tailscale
    depends_on:
      - tailscale
    environment:
      - TZ=${TZ:-UTC}
      - BASE_URL=http://${TS_HOSTNAME:-zerobyte}:4096
      - APP_SECRET=${APP_SECRET}
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/lib/zerobyte:/var/lib/zerobyte
```

</Step>
<Step>

### Create .env file

```bash .env
TS_AUTHKEY=tskey-auth-xxxxx
TS_HOSTNAME=zerobyte
APP_SECRET=your-secret-here  # Generate with: openssl rand -hex 32
TZ=UTC
# Optional:
# TS_EXTRA_ARGS=--advertise-tags=tag:backup
# TS_USERSPACE=false
```

</Step>
<Step>

### Start the stack

```bash
docker compose up -d
```

</Step>
<Step>

### Verify in Tailscale

Confirm the node appears in your [Tailscale admin console](https://login.tailscale.com/admin/machines). Approve it if your policy requires approval.

</Step>
</Steps>

## Accessing Zerobyte

Once running, access Zerobyte over your tailnet:

- **By IP**: `http://<tailscale-ip>:4096`
- **By MagicDNS**: `http://zerobyte:4096` (if MagicDNS is enabled)

To make Zerobyte accessible **only** via Tailscale, remove the `ports:` section from the `tailscale` service.

## Kernel Mode vs Userspace Mode

| Mode | Requirements | Best For |
|------|-------------|----------|
| **Kernel** (default) | `/dev/net/tun`, `NET_ADMIN` | Linux servers, best performance |
| **Userspace** | No special devices | Docker Desktop, restricted hosts |

To use userspace mode:
1. Set `TS_USERSPACE=true` in your `.env`
2. Remove the `devices: /dev/net/tun` section from the tailscale service

## Tailscale-Only Access

If you want Zerobyte to be reachable **only** via Tailscale (not from the local network), remove the `ports:` section from the `tailscale` service. Zerobyte will still be able to access the internet and your LAN for backup operations, but the UI will only be accessible over Tailscale.

## Troubleshooting

- **TUN device error**: Ensure `/dev/net/tun` exists on the host. Switch to userspace mode if it doesn't.
- **Node not appearing**: Check the auth key is valid and not expired.
- **ACL issues**: Set `TS_EXTRA_ARGS=--advertise-tags=tag:backup` and configure ACLs in Tailscale admin.

Verify the tailnet address:

```bash
docker exec zerobyte-tailscale tailscale status
docker exec zerobyte-tailscale tailscale ip -4
```

import { Step, Steps } from "fumadocs-ui/components/steps";

---

--- METRICS ---
- Files Extracted: 45
- Estimated Token Budget: ~68670 tokens
- Canonical Reference: https://codewiki.google/github.com/nicotsx/zerobyte
