zerobyte Documentation

Backup automation for self-hosters. Built on top of restic

7,066 stars TypeScript TypeScript 45 files Β· ~67,630 tokens #backup#backup-utility#restic#self-hosted
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy zerobyte on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name zerobyte --restart always rustfs/rustfs:latest
Configured Services & Ports
βš™οΈ rustfs
image: rustfs/rustfs:latest
βš™οΈ rustfs-setup
image: rustfs/rc:latest
βš™οΈ sftp
image: build locally
βš™οΈ sftp-legacy-rsa
image: build locally
βš™οΈ webdav
image: build locally
βš™οΈ smb
image: build locally
βš™οΈ nfs
image: build locally
βš™οΈ integration
image: build locally
docker-compose.yml
yaml (docker-compose.yml)
services:
  rustfs:
    image: rustfs/rustfs:latest
    command: ["/data"]
    environment:
      RUSTFS_ADDRESS: ":9000"
      RUSTFS_ACCESS_KEY: "rustfsadmin"
      RUSTFS_SECRET_KEY: "rustfsadmin"
      RUSTFS_CONSOLE_ENABLE: "false"
    volumes:
      - rustfs-data:/data

  rustfs-setup:
    image: rustfs/rc:latest
    depends_on:
      - rustfs
    entrypoint: ["/bin/sh", "-c"]
    command:
      - |
        rc alias set rustfs http://rustfs:9000 rustfsadmin rustfsadmin --signature v4 --bucket-lookup path --region us-east-1

        until rc ready rustfs --timeout 1; do
          echo "Waiting for RustFS..."
          sleep 1
        done

        rc bucket create --ignore-existing --region us-east-1 rustfs/zerobyte-integration

  sftp:
    build:
      context: ./sftp
    environment:
      SFTP_USER: "zerobyte-sftp"
      SFTP_PASSWORD: "zerobyte-sftp-password"
      SFTP_PUBLIC_KEY_PATH: "/run/zerobyte/sftp/id_ed25519.pub"
    volumes:
      - ../artifacts/sftp/id_ed25519.pub:/run/zerobyte/sftp/id_ed25519.pub:ro
      - sftp-data:/srv/zerobyte-integration
    healthcheck:
      test: ["CMD", "ssh-keyscan", "-T", "2", "localhost"]
      interval: 1s
      timeout: 5s
      retries: 30

  sftp-legacy-rsa:
    build:
      context: ./sftp
    environment:
      SFTP_USER: "zerobyte-sftp"
      SFTP_PASSWORD: "zerobyte-sftp-password"
      SFTP_PUBLIC_KEY_PATH: "/run/zerobyte/sftp/id_ed25519.pub"
      SFTP_LEGACY_RSA_HOSTKEY: "true"
    volumes:
      - ../artifacts/sftp/id_ed25519.pub:/run/zerobyte/sftp/id_ed25519.pub:ro
      - sftp-legacy-rsa-data:/srv/zerobyte-integration
    healthcheck:
      test: ["CMD", "ssh-keyscan", "-T", "2", "-t", "rsa", "localhost"]
      interval: 1s
      timeout: 5s
      retries: 30

  webdav:
    build:
      context: ./webdav
    environment:
      WEBDAV_USER: "zerobyte-webdav"
      WEBDAV_PASSWORD: "zerobyte-webdav-password"
    healthcheck:
      test:
        [
          "CMD",
          "curl",
          "-fsS",
          "-u",
          "zerobyte-webdav:zerobyte-webdav-password",
          "http://localhost/zerobyte-integration/case-a/hello.txt",
        ]
      interval: 1s
      timeout: 5s
      retries: 30

  smb:
    build:
      context: ./smb
    environment:
      SMB_USER: "zerobyte-smb"
      SMB_PASSWORD: "zerobyte-smb-password"
      SMB_SHARE: "zerobyte-integration"
    healthcheck:
      test:
        [
          "CMD",
          "smbclient",
          "//localhost/zerobyte-integration",
          "-U",
          "zerobyte-smb%zerobyte-smb-password",
          "-c",
          "get case-a/hello.txt /tmp/zerobyte-smb-healthcheck.txt",
        ]
      interval: 1s
      timeout: 5s
      retries: 30

  nfs:
    build:
      context: ./nfs
    privileged: true
    volumes:
      - nfs-data:/srv/zerobyte-integration
    healthcheck:
      test: ["CMD", "sh", "-c", "exportfs -v | grep -q /srv/zerobyte-integration/fixtures"]
      interval: 1s
      timeout: 5s
      retries: 30

  integration:
    build:
      context: ../../../..
      dockerfile: app/test/integration/infra/Dockerfile
      args:
        BASE_IMAGE: zerobyte-integration-runtime-base:latest
    depends_on:
      rustfs-setup:
        condition: service_completed_successfully
      sftp:
        condition: service_healthy
      sftp-legacy-rsa:
        condition: service_healthy
      webdav:
        condition: service_healthy
      smb:
        condition: service_healthy
      nfs:
        condition: service_healthy
    cap_add:
      - SYS_ADMIN
    devices:
      - /dev/fuse:/dev/fuse
    volumes:
      - ../artifacts:/app/app/test/integration/artifacts
      - ../artifacts/sftp/id_ed25519:/run/zerobyte/sftp/id_ed25519:ro
    environment:
      LOG_LEVEL: "error"
      SFTP_PRIVATE_KEY_PATH: "/run/zerobyte/sftp/id_ed25519"
      SKIP_VOLUME_MOUNT_INTEGRATION_TESTS: "${SKIP_VOLUME_MOUNT_INTEGRATION_TESTS:-false}"

volumes:
  rustfs-data:
  sftp-data:
  sftp-legacy-rsa-data:
  nfs-data:

Technical Documentation: nicotsx/zerobyte

ℹ️ Provenance: Multi-Tier Ingestion Β· CodeWiki Reference

Overview (`README.md`)

Join the community

[!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.

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.

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 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.

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.

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 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 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.

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 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.

  • 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 file.

Third-party software

This project includes the following third-party software components:

Restic

Zerobyte includes Restic for backup functionality.

For a complete list of third-party software licenses and attributions, please refer to the 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 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

Advanced setups

  • Tailscale sidecar β€” 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
  1. Edit .env and set HOST_DATA_DIR to the directory you want to back up.

  2. 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
  1. 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
  1. Edit provisioning.json:
  • Replace organizationId with your existing Zerobyte organization ID
  • Update the S3 endpoint/bucket values
  • Update the WebDAV server, path, and username
  1. Edit secrets/aws_secret_access_key and replace the placeholder value with the real secret access key.

  2. 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
  1. Edit .env and set RCLONE_CONFIG_DIR to the absolute path of your host rclone config directory.

  2. 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. 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):

  • 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, 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 is a fast all-in-one JavaScript runtime.


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


title: Zerobyte Documentation

Welcome to Zerobyte, a powerful backup automation platform built on Restic.

Getting Started

Learn about Zerobyte's features and capabilities

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

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

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

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
Zerobyte is still in version 0.x.x and is subject to major changes between versions. Core features are under active development

Explore the Docs

} href="/docs/concepts/volumes"> Understand volumes, repositories, and backup jobs Step-by-step guides for rclone, reverse proxies, Tailscale, and more

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

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

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

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

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

This guide assumes you've already [installed Zerobyte](/docs/installation) and can access the web interface.

Overview

Setting up a backup involves three key steps:

Add a Volume

Define the source data you want to backup

Create a Repository

Set up an encrypted storage destination for your backups

Configure a Backup Job

Connect your volume to your repository with scheduling and retention policies

Let's walk through each step.

First-Time Setup

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

Open Zerobyte

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

Create the Admin User

On the Onboarding page, enter:

  • Email
  • Username
  • Password
  • Confirm Password

Then click Create admin user.

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.

Continue to Volumes

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

**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. After onboarding is complete, future visits use the normal **Login** page instead of the onboarding flow.

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

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

Create New Volume

Click the Create Volume button

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)

Save Volume

Click Create to save the volume configuration

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.

Adding Remote Volumes

Zerobyte also supports remote volume types:

<Tabs items={['NFS', 'SMB/CIFS', 'WebDAV', 'SFTP']}>

Configure an NFS share:

text
- **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
Configure an SMB/CIFS share:
text
- **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`)
Configure a WebDAV server:
text
- **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
Configure an SFTP server:
text
- **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**
Remote volumes require the full installation with `SYS_ADMIN` capability and `/dev/fuse` device. See the [Installation guide](/docs/installation) for details.

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:

Click on Repositories in the sidebar

Create New Repository

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

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

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.

Create Repository

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

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. 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.

Cloud Storage Repositories

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

<Tabs items={['S3-Compatible', 'Google Cloud Storage', 'Azure Blob', 'rclone']}>

Configure S3-compatible storage (AWS S3, MinIO, Wasabi, Backblaze B2, etc.):

text
```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
```
Configure Google Cloud Storage:
text
```yaml
Type: Google Cloud Storage
Bucket: my-backup-bucket
Project ID: my-project-id
Service Account JSON: Paste the service account JSON
```
Configure Azure Blob Storage:
text
```yaml
Type: Azure Blob Storage
Container: backups
Account Name: myaccountname
Account Key: my-account-key
Endpoint Suffix: core.windows.net (optional)
```
Configure rclone-based storage (requires rclone config mounted):
text
```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>
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.

Step 3: Create a Backup Job

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

Click on Backups in the sidebar

Start a New Backup Job

Click Create a backup job

Choose the Volume

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

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

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.

- `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

Configure Retention Policy

Define how long backups should be kept:

yaml
Keep Daily: 7
Keep Weekly: 4
Keep Monthly: 6
Keep Yearly: 1
Retention policies automatically prune old backups to save storage space. Restic's deduplication ensures you only store unique data.

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/**)

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

Save Backup Job

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

Step 4: Run Your First Backup

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

Open the Backup Job Details

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

Run Backup Manually

Click Backup now

Monitor Progress

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

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.

During the first backup, all data is uploaded. Subsequent backups are much faster due to Restic's incremental backup and deduplication.

Step 5: Verify Your Backup

After the backup completes, verify it was successful:

Open the Snapshot

In the Snapshots section, click the snapshot you just created

Check Snapshots

You should see snapshot details such as:

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

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

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
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.

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
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`.

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
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).

Next Steps

Now that you have a working backup, consider:

}> Back up additional directories or remote shares

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

Get alerted when backups fail or complete

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

Common Tasks

Restoring Data

To restore data from a backup:

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

Select Snapshot

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

Browse and Select

Browse the file tree and select files/folders to restore

Choose Restore Location

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

Restore

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

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

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
Deleting snapshots is irreversible. Ensure you don't need the data before deleting.

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:

}> View detailed logs with `docker compose logs -f zerobyte`

<Card title="GitHub Issues" icon={}>
Report bugs or request features at github.com/nicotsx/zerobyte/issues

Explore the full documentation for advanced topics Join discussions and get help from other users
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

Conclusion

You've successfully:

  • Created an admin account and downloaded the recovery key
  • Added a volume to backup
  • Created a repository for encrypted storage
  • Configured and ran your first backup job
  • Verified your backup was successful
  • 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"],

    text
    /* 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

    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 [email protected]
    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

    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
    Never share or commit your `APP_SECRET`. If you lose it, encrypted data (credentials stored for volumes and repositories) cannot be recovered. Zerobyte reads the contents of `APP_SECRET_FILE`, trims surrounding whitespace and newlines, and applies the same 32-256 character requirement as `APP_SECRET`.
    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. Restic default
    TRUST_PROXY Trust forwarded host and protocol headers for authentication. See 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 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
    **Do not** point `/var/lib/zerobyte` to a network share. This causes permission issues and severe performance degradation. Always use local storage. **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

    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"

    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
    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. `TRUSTED_ORIGINS` only allows additional origins for CORS. It does **not** disable secure cookies or make HTTP access work when `BASE_URL` is HTTPS.

    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.

    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.

    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
    Always check the [release notes](https://github.com/nicotsx/zerobyte/releases) before updating, especially for v0.x.x versions which may include breaking changes.

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


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


    title: Installation

    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

    Install Docker

    Ensure Docker is installed on your server. Visit docs.docker.com for installation instructions for your platform.

    Install Docker Compose

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

    Check Installation

    Verify your installation by running:

    bash
    docker --version
    docker compose version

    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
    **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 before using mounted volumes for metadata-sensitive backups.

    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
    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)

    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
    **Important**: Do not point `/var/lib/zerobyte` to a network share. This will cause permission issues and severe performance degradation. Always use local storage. **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.

    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:

    text
    http://<your-server-ip>:4096
    On first access, you'll be prompted to create an admin account. This account will have full access to all backup management features.

    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:

  • Improved security by removing `SYS_ADMIN` capability
  • Support for local directories mounted into the container
  • All repository types still supported (local, S3, GCS, Azure, rclone)
  • Cannot mount remote shares (NFS, SMB, WebDAV, SFTP) from within Zerobyte
  • 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 for the practical tradeoffs.

    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
    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.

    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
    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.

    Mounting rclone Configuration

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

    Configure rclone on Host

    Install and configure rclone on your host system:

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

    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

    Restart Container

    bash
    docker compose down && docker compose up -d

    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 for full configuration examples.

    Next Steps

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

    } href="/docs/quickstart"> Set up your first volume, repository, and backup job

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

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


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


    title: Introduction

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

    Zerobyte is still in version 0.x.x and is subject to major changes between versions. Core features are under active development

    Key Features

    }> Schedule encrypted, compressed backups with fine-grained retention policies powered by Restic

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

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

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

    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:

    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.

    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.

    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.

    All backups are encrypted using Restic's encryption. Your encryption password is securely stored and never leaves your server.

    Use Cases

    Back up your home server data (photos, documents, media) to cloud storage or an external drive with automatic scheduling and retention. Protect your NAS data by backing up critical shares to offsite cloud storage with encryption and versioning. Automatically back up development servers, databases, and configuration files to ensure quick recovery. Centralize backup management for multiple locations, backing up various sources to different repository types.

    Why Restic?

    Zerobyte is built on Restic 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

    } href="/docs/installation"> Deploy Zerobyte with Docker and Docker Compose

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

    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

    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
    Never share sensitive information (passwords, access keys, personal data) in public issue reports. Redact them from logs before posting.

    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)"]}>

    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

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

    yaml
    security_opt:
      - seccomp:unconfined

    Adjust the SELinux context:

    yaml
    security_opt:
      - label:type:container_runtime_t

    Or disable SELinux enforcement for the container:

    yaml
    security_opt:
      - label:disable
    **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.

    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
    `/dev/fuse` is **not required** for SMB/CIFS or NFS mounts, only for SFTP and rclone volumes.

    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 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 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 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 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.

    Only unlock if you're certain no other backup or restore operations are running against the repository.

    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

    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

    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 (your data source)
    • Where to store it, a repository (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
    Choose **Manual only** if you want to create the job without scheduled runs and trigger it yourself.

    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
    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.

    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
    Retention rules are additive. A snapshot that satisfies any rule is kept. Restic determines which snapshots best represent each time period automatically.

    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
    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.

    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 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.

    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.

    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.

    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.

    At a high level, restoring works like this:

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

    Select a snapshot

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

    Browse and select files

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

    Choose a restore location

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

    Restore

    Click Restore and wait for the operation to complete.

    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.

    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.

    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

    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. Keeping every snapshot indefinitely wastes storage. A policy like 7 daily, 4 weekly, 6 monthly, and 1 yearly provides good coverage without excessive cost. 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. Excluding directories like `node_modules/`, `.cache/`, and `*.tmp` files reduces backup size and duration without losing important data. 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.

    Next steps

    } href="/docs/configuration"> Configure environment variables, Docker secrets, and deployment options

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

    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

    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.
    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`.

    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.

    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.

    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 if you manage repositories and volumes declaratively.

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


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


    title: Repositories

    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.

    Zerobyte automatically creates a subdirectory under the given path to keep each repository isolated.

    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 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.

    The service account needs permissions to create, read, and delete objects in the target bucket.

    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.

    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.

    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.

    For production use, keep host key verification enabled and provide the server's known host key to prevent man-in-the-middle attacks.

    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 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:

    Restic automatically decides whether to compress each data chunk. This provides the best balance of speed and space savings for most workloads. 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. 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.

    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.
    Bandwidth limits are enforced at the application layer. Actual network usage may be slightly higher due to protocol overhead.

    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.

    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.

    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.

    Only run unlock if you are certain no other operations are actively accessing the repository.

    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 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

    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. 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. Schedule the Doctor operation monthly or quarterly for production repositories to verify data integrity, rebuild indexes, and reclaim space from deleted snapshots. 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. 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.

    Next steps

    } href="/docs/concepts/backups"> Learn how backup jobs create snapshots and manage retention

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

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

    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

    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.

    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).

    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"]}>

    ### Directory (local)

    text
    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>
    ### NFS (Network File System)
    text
    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>
    ### SMB/CIFS
    text
    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>
    ### WebDAV
    text
    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>
    ### SFTP (SSH File Transfer Protocol)
    text
    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>
    ### Rclone (40+ cloud providers)
    text
    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>

    Volume status

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

    Mounted

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

    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.

    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.

    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.

    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.

    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.

    For a complete walkthrough of managing secrets through environment variables, Docker secrets, and provisioning files, see the [Provisioning guide](/docs/guides/provisioning).

    Best practices

    Use names that clearly identify what data the volume contains and where it comes from. Good examples:
    text
    - `production-database-dumps`
    - `customer-uploads-nfs`
    - `accounting-smb-share`
    
    Avoid generic names like `volume1` or `backup`, they become confusing as you add more 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. Mounting volumes in read-only mode prevents any chance of the backup process modifying source data. This is especially important for:
    text
    - Live production systems
    - Archive or compliance data
    - Shared storage used by multiple applications
    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.

    Next steps

    Learn about encrypted storage destinations where your backup snapshots are kept Configure backup jobs that connect your volumes to repositories with scheduling and retention

    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

    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
    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.

    How to do it with Zerobyte

    Create the volume for your live data

    Add the directory or remote share you want to protect as a volume.

    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.

    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.

    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.

    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.

    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.

    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.

    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

    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

    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: 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.

    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`.

    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 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.
    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.

    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

    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.

    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?

    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.
    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.


    Apps Docs Content Docs Guides Notifications (`apps/docs/content/docs/guides/notifications.mdx`)


    title: Notifications

    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.
    Create destinations once, then reuse them across as many backup schedules as you want.

    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
    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.

    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",
    ]}

    Use Email when you want alerts to go to people, shared mailboxes, or downstream systems that already process email.
    text
    **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: [email protected]
    Password: <smtp-password>
    From Address: [email protected]
    From Name: Zerobyte Alerts
    To Addresses: [email protected], [email protected]
    Use TLS: On
    ```
    Use Slack for shared operational visibility in a team channel.
    text
    **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:
    ```
    Use Discord when your team lives in a server channel, or when you want to post to a specific thread.
    text
    **Fields**
    
    - `Webhook URL`
    - `Bot Username` (optional)
    - `Avatar URL` (optional)
    - `Thread ID` (optional)
    Use Gotify when you want self-hosted push with explicit priority control.
    text
    **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`.
    Use ntfy when you want a simple topic-based push service, either on `ntfy.sh` or your own server.
    text
    **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
    ```
    Use Pushover for direct, personal push notifications.
    text
    **Fields**
    
    - `User Key`
    - `API Token`
    - `Devices` (optional)
    - `Priority` as `-1`, `0`, or `1`
    Use Telegram when you want notifications in a bot chat or a group topic.
    text
    **Fields**
    
    - `Bot Token`
    - `Chat ID`
    - `Thread ID` (optional)
    
    **Example**
    
    ```text
    Type: Telegram
    Bot Token: 123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
    Chat ID: -1231234567890
    Thread ID: 3
    ```
    Use Generic Webhook when another service expects a normal HTTP request instead of a provider-specific webhook format.
    text
    **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
    ```
    Use Custom when you already know the exact Shoutrrr URL you want Zerobyte to use.
    text
    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
    ```

    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

    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

    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.

    Zerobyte SSO is invite-oriented. Enabling auto-linking does **not** mean "anyone with that email can join".

    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.

    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.

    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.
    The safest mental model is: auto-linking can help an already eligible account add SSO, but it is not an open-enrollment feature.

    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.

    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

    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.

    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.

    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

    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
    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.

    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
    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.

    Quick Start

    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"
    			}
    		}
    	]
    }

    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

    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.

    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"]}>

    | Field | Required | Notes |
    | ------ | -------- | ---------------------------------- |
    | path | yes | Path inside the Zerobyte container |

    text
    ```json
    {
    	"backend": "local",
    	"path": "/var/lib/zerobyte/repositories/primary",
    	"isExistingRepository": false
    }
    ```
    | Field | Required | Notes | | ----------------- | -------- | ----------------------------------- | | `endpoint` | yes | Example: `https://s3.amazonaws.com` | | `bucket` | yes | Bucket name | | `accessKeyId` | yes | Supports secret references | | `secretAccessKey` | yes | Supports secret references |
    text
    ```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
    }
    ```
    | Field | Required | Notes | | ----------------- | -------- | ------------------------------ | | `endpoint` | yes | Your Cloudflare R2 S3 endpoint | | `bucket` | yes | Bucket name | | `accessKeyId` | yes | Supports secret references | | `secretAccessKey` | yes | Supports secret references |
    text
    ```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
    }
    ```
    | Field | Required | Notes | | ----------------- | -------- | ------------------------------------------------ | | `bucket` | yes | Bucket name | | `projectId` | yes | Google Cloud project ID | | `credentialsJson` | yes | Service account JSON. Supports secret references |
    text
    ```json
    {
    	"backend": "gcs",
    	"bucket": "zerobyte-backups",
    	"projectId": "my-gcp-project",
    	"credentialsJson": "file://gcs_credentials_json",
    	"isExistingRepository": true
    }
    ```
    | Field | Required | Notes | | ---------------- | -------- | -------------------------- | | `container` | yes | Blob container name | | `accountName` | yes | Storage account name | | `accountKey` | yes | Supports secret references | | `endpointSuffix` | no | Custom endpoint suffix |
    text
    ```json
    {
    	"backend": "azure",
    	"container": "zerobyte-backups",
    	"accountName": "storageaccount",
    	"accountKey": "env://AZURE_STORAGE_ACCOUNT_KEY",
    	"endpointSuffix": "core.windows.net",
    	"isExistingRepository": true
    }
    ```
    | Field | Required | Notes | | -------- | -------- | ------------------------------------ | | `remote` | yes | Name of the configured rclone remote | | `path` | yes | Path inside that remote |
    text
    ```json
    {
    	"backend": "rclone",
    	"remote": "remote-name",
    	"path": "zerobyte/backups",
    	"isExistingRepository": true
    }
    ```
    | 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 |
    text
    ```json
    {
    	"backend": "rest",
    	"url": "https://rest-server.example.com",
    	"username": "env://REST_USERNAME",
    	"password": "file://rest_password",
    	"path": "zerobyte",
    	"isExistingRepository": true
    }
    ```
    | 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 |
    text
    ```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..."
    }
    ```

    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"]}>

    | Field | Required | Notes |
    | ---------- | -------- | ---------------------------------- |
    | path | yes | Path inside the Zerobyte container |
    | readOnly | no | If provided, it must be false |

    text
    ```json
    {
    	"backend": "directory",
    	"path": "/data/documents"
    }
    ```
    | 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 |
    text
    ```json
    {
    	"backend": "nfs",
    	"server": "10.0.0.10",
    	"exportPath": "/exports/media",
    	"port": 2049,
    	"version": "4.1",
    	"readOnly": true
    }
    ```
    | 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 |
    text
    ```json
    {
    	"backend": "smb",
    	"server": "fileserver.local",
    	"share": "team",
    	"username": "backup-user",
    	"password": "env://SMB_PASSWORD",
    	"vers": "3.0",
    	"domain": "WORKGROUP",
    	"port": 445,
    	"readOnly": false
    }
    ```
    | 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 |
    text
    ```json
    {
    	"backend": "webdav",
    	"server": "cloud.example.com",
    	"path": "/team-a",
    	"username": "team-a",
    	"password": "env://WEBDAV_PASSWORD",
    	"port": 443,
    	"ssl": true
    }
    ```
    | 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 |
    text
    ```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..."
    }
    ```
    | Field | Required | Notes | | ---------- | -------- | ------------------------------------ | | `remote` | yes | Name of the configured rclone remote | | `path` | yes | Path inside that remote | | `readOnly` | no | Mount the volume read-only |
    text
    ```json
    {
    	"backend": "rclone",
    	"remote": "remote-name",
    	"path": "team-a",
    	"readOnly": true
    }
    ```

    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
    `file://` references always resolve from `/run/secrets/` and must be a single filename, not a nested path.

    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

    Each provisioned entry must reference an existing `organizationId`. Complete first-run setup before enabling provisioning so you have a valid organization ID.
    • 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

    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.

    Install rclone

    Install rclone on your host system:

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

    Alternatively, visit rclone.org/install for platform-specific packages.

    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.

    Verify your remotes

    List all configured remotes:

    bash
    rclone listremotes

    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.

    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
    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

    Using rclone for repositories

    Rclone repositories let you store encrypted backups on any rclone-supported provider.

    Open the Create Repository form

    Navigate to Repositories in the sidebar and click Create Repository.

    Select rclone as the type

    Choose rclone from the repository type dropdown.

    Choose your remote

    Select your remote from the dropdown list. Zerobyte reads all configured remotes from the mounted rclone config.

    Specify a path

    Enter a path within the remote where backups should be stored (e.g., backups/zerobyte).

    Create the repository

    Click Create and Initialize. Zerobyte will initialize a new encrypted Restic repository on the remote.

    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.

    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.

    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

    Create a new volume

    Navigate to Volumes in the sidebar and click Create Volume.

    Select rclone as the type

    Choose rclone from the volume type dropdown.

    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).

    Save and mount

    Click Create. Zerobyte will mount the remote as a FUSE filesystem and make it available for backup jobs.

    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.

    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"]}>

    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).

    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.

    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.

    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.

    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:

    Restart the container

    After refreshing the token, restart the Zerobyte container so it picks up the updated config:

    bash
    docker compose restart
    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.

    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.
    For more detailed troubleshooting steps, see the [Troubleshooting](/docs/troubleshooting) page.

    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

    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
    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.

    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.

    Store the export and its passphrase separately. Anyone with both can decrypt the credentials and recovery key contained in the export.

    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.

    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

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

    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.

    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, 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 must 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 for configuration examples.

    Connect a machine

    1. Open Settings, choose your organization, and find Machines.
    2. Click Connect machine and give the machine a name.
    3. Copy the installation command and run it in a terminal on that machine.

    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.

    Choose folders to back up

    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.

    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

    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.

    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.

    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.

    Only run Unlock when you are certain no real backup, restore, or maintenance operation is still using that repository.

    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.

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


    Apps Docs Content Docs Guides Restoring (`apps/docs/content/docs/guides/restoring.mdx`)


    title: Restoring Data

    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.

    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.

    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

    Open the backup job

    Go to Backups and open the job that contains the data you need.

    Choose the snapshot

    Select the snapshot from the time period you want to recover.

    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.

    Choose the destination

    Pick either:

    • Original location, to put the data back where it came from
    • Custom location, to restore into another path

    Review overwrite behavior

    Choose how Zerobyte should handle files that already exist at the target.

    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.

    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
    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.

    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.

    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.

    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

    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

    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

    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.
    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.

    Proxy configurations

    <Tabs items={["Nginx", "Caddy", "Traefik"]}>

    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

    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.

    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.

    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

    **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.

    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:

    Test HTTPS access

    Open https://zerobyte.example.com in your browser. You should see the Zerobyte login page with a valid TLS certificate.

    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

    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.

    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

    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 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

    Setup

    This example uses a sidecar networking pattern where Zerobyte shares the Tailscale container's network namespace.

    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

    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

    Start the stack

    bash
    docker compose up -d

    Verify in Tailscale

    Confirm the node appears in your Tailscale admin console. Approve it if your policy requires approval.

    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 ---