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.
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/zerobyteto a network share. You will face permission issues and strong performance degradation.
[!NOTE]
TrueNAS Users: The host path/var/libis 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:yamlvolumes: - /etc/localtime:/etc/localtime:ro - /mnt/tank/docker/zerobyte:/var/lib/zerobyteThis ensures your configuration, encryption keys, and database persist across TrueNAS upgrades.
Then, run the following command to start Zerobyte:
docker compose up -dOnce 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:
environment:
- GOMAXPROCS=2This 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
offto reduce CPU usage, orautofor the usual balance.maxcan 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:
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:/mydataTrade-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:
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:/mydataAfter updating the compose.yaml file, restart the Zerobyte container to apply the changes:
docker compose down
docker compose up -dNow, 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:
Install rclone on your host system (if not already installed):
bashcurl https://rclone.org/install.sh | sudo bashConfigure your cloud storage remote using rclone's interactive config:
bashrclone configFollow the prompts to set up your cloud storage provider. For OAuth providers (Google Drive, Dropbox, etc.), rclone will guide you through the authentication flow.
Verify your remote is configured:
bashrclone listremotesMount the rclone config into the Zerobyte container by updating your
compose.yaml:diffservices: 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:roNote 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:yamlenvironment: - RCLONE_CONFIG_DIR=/home/appuser/.config/rclone volumes: - ~/.config/rclone:/home/appuser/.config/rclone:roRestart the Zerobyte container:
bashdocker compose down docker compose up -dCreate 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.
Cookie security
- IP Address/HTTP access: Set
BASE_URL=http://192.168.1.50:4096(or your IP). Cookies will useSecure: false, allowing immediate login without SSL. - Domain/HTTPS access: Set
BASE_URL=https://zerobyte.example.com. Cookies will automatically useSecure: truefor protected sessions.
Reverse proxy setup
If you're running Zerobyte behind a reverse proxy (Nginx, Traefik, Caddy, etc.):
- Set
BASE_URLto your HTTPS domain (e.g.,https://zerobyte.example.com) - The app will automatically enable secure cookies based on the
https://prefix - Ensure your proxy passes the
X-Forwarded-Protoheader
Important notes
- The
BASE_URLmust start withhttps://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_ORIGINSonly allows additional origins for auth-related requests. It does not disable secure cookies or make authenticated HTTP access work whenBASE_URLis HTTPS.- If
BASE_URLis 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.
- License: BSD 2-Clause License
- Copyright: Copyright (c) 2014, Alexander Neumann [email protected]
- Status: Included unchanged
- License Text: See LICENSES/BSD-2-Clause-Restic.txt
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:
bun install
bun run devFor local development, create a .env.local file at the repo root and override the Docker paths:
# 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://*.localhostNotes:
- 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
resticon your machine (e.g., via Homebrew). Ifresticis not installed, the app still starts but backup operations will fail with a clear error.
Overview (`examples/README.md`)
Examples
This folder contains runnable, copy/paste-friendly examples for running Zerobyte in different setups.
Table of contents
Basic usage
- Basic Docker Compose β standard deployment with remote mount support (includes
SYS_ADMIN+/dev/fuse). - Simplified Docker Compose (no remote mounts) β reduced-privilege deployment (no
SYS_ADMIN, no/dev/fuse). - Bind-mount a local directory β back up a host folder by mounting it into the container.
- Mount an rclone config β use rclone-based repository backends by mounting your rclone config.
- Provisioned repositories and volumes β operator-managed provisioning with env/file references kept in a mounted JSON file.
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
bun run dev:agent user@linux-serverStart 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
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 bytes10K= 10 kilobytes5M= 5 megabytes1G= 1 gigabyte
Examples
# 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 -nOverview (`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
cp .env.example .env
docker compose up -dAccess
- UI/API:
http://<host>:4096
Notes
- This example uses
cap_add: SYS_ADMINand/dev/fuseto support mounting remote volumes. - Do not place
/var/lib/zerobyteon 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
- Copy the env file:
cp .env.example .envEdit
.envand setHOST_DATA_DIRto the directory you want to back up.Start the stack:
docker compose up -dUse 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.yamlmounts a provisioning file and a Docker secret..env.exampleprovides the environment variables used byenv://...references.provisioning.example.jsonprovisions one S3 repository and one WebDAV volume.secrets/aws_secret_access_key.exampleshows the file consumed byfile://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
- Copy the example files:
cp .env.example .env
cp provisioning.example.json provisioning.json
cp secrets/aws_secret_access_key.example secrets/aws_secret_access_key- Edit
.env:
- Set
APP_SECRETto a real secret, for exampleopenssl rand -hex 32, or setAPP_SECRET_FILEto a mounted secret file - Set
ZEROBYTE_AWS_ACCESS_KEY_ID - Set
ZEROBYTE_WEBDAV_PASSWORD - Adjust
BASE_URLandTZif needed
- Edit
provisioning.json:
- Replace
organizationIdwith your existing Zerobyte organization ID - Update the S3 endpoint/bucket values
- Update the WebDAV server, path, and username
Edit
secrets/aws_secret_access_keyand replace the placeholder value with the real secret access key.Start the stack:
docker compose up -dHow secret references work
env://ZEROBYTE_AWS_ACCESS_KEY_IDreads from a container environment variable.env://ZEROBYTE_WEBDAV_PASSWORDreads from a container environment variable.file://aws_secret_access_keyreads/run/secrets/aws_secret_access_keyinside 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 Backupsappears in the repositories list as a managed repository.Team A WebDAVappears 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_ADMINand/dev/fuseenabled because the sample volume uses WebDAV. - Each provisioned entry must reference an existing
organizationId. - Each entry includes both a top-level
backendand the matchingconfig.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:
rclone configSetup
- Copy the env file:
cp .env.example .envEdit
.envand setRCLONE_CONFIG_DIRto the absolute path of your host rclone config directory.Start the stack:
docker compose up -dAccess
- UI/API:
http://<host>:4096
Notes
- This setup does not require
SYS_ADMINor/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
cp .env.example .env
docker compose up -dAccess
- 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
tailscalecontainer brings up a Tailscale node - The
zerobytecontainer shares thetailscalenetwork 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/tunon the host - requires
NET_ADMIN - may require
SYS_MODULEon some hosts (kept commented out in the compose file)
- requires
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)
- does not require
Setup
Copy the env file and fill in your auth key:
bashcp .env.example .envStart the stack:
bashdocker compose up -dIn the Tailscale admin console, confirm the node is present (and approved if your policy requires it).
Access
- Over Tailscale:
http://<tailscale-ip>:4096orhttp://<tailscale-name>:4096(if MagicDNS is enabled) - Locally (optional): the example publishes
4096:4096on 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:tailscalemakes Zerobyte share the Tailscale containerβs entire network namespace (interfaces + routing table).- Traffic to tailnet IPs (typically
100.x.y.z) goes overtailscale0and 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-routesis 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-dnsis used, Zerobyte will also use Tailscaleβs DNS servers. - Zerobyte still needs
SYS_ADMINand/dev/fuseif 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 totailscale up)TS_USERSPACE(optional; set totrueto use userspace mode)TZ(optional)
Troubleshooting
- If the
tailscalecontainer canβt start due to missing TUN support, ensure your host has/dev/net/tunavailable and that Docker is allowed to use it. - If your tailnet uses ACLs/tags, set
TS_EXTRA_ARGSaccordingly (for example--advertise-tags=tag:backup). - If the
tailscalecontainer fails due to Docker Desktop / missing TUN support: setTS_USERSPACE=truein your.env, remove the/dev/net/tun:/dev/net/tundevice mapping in compose.yaml, and keepSYS_MODULEdisabled (commented out).
To confirm the tailnet address of the container:
docker exec zerobyte-tailscale tailscale status
docker exec zerobyte-tailscale tailscale ip -4To 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)
docker exec zerobyte-tailscale ip route
docker exec zerobyte-tailscale ip route show table 52Overview (`packages/core/README.md`)
core
To install dependencies:
bun installTo run:
bun run index.tsThis 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
<Card title="Installation" icon={
Deploy Zerobyte with Docker and Docker Compose
<Card title="Quick Start" icon={
Set up your first backup in minutes
<Card title="3-2-1 Strategy" icon={
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
Explore the Docs
<Card title="Configuration" icon={
Environment variables and Docker configuration reference
<Card title="CLI Reference" icon={
Admin commands for account recovery and maintenance
<Card title="Troubleshooting" icon={
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.
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:
- 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.
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:
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 lineRestart the container to apply changes:
docker compose down
docker compose up -dCreate the Volume in Zerobyte
Navigate to Volumes
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
Adding Remote Volumes
Zerobyte also supports remote volume types:
<Tabs items={['NFS', 'SMB/CIFS', 'WebDAV', 'SFTP']}>
Configure an NFS share:
- **Server**: NFS server hostname or IP
- **Export Path**: NFS export path (e.g., `/mnt/data`)
- **Port**: NFS port (default: `2049`)
- **Version**: `3`, `4`, or `4.1`
- **Read-only Mode**: Optional, recommended for backup sources- **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`)- **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- **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**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:
Navigate to Repositories
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.
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.):
```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
``````yaml
Type: Google Cloud Storage
Bucket: my-backup-bucket
Project ID: my-project-id
Service Account JSON: Paste the service account JSON
``````yaml
Type: Azure Blob Storage
Container: backups
Account Name: myaccountname
Account Key: my-account-key
Endpoint Suffix: core.windows.net (optional)
``````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>Step 3: Create a Backup Job
A backup job connects a volume to a repository and defines when and how backups should run.
Navigate to Backups
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.
Configure Retention Policy
Define how long backups should be kept:
Keep Daily: 7
Keep Weekly: 4
Keep Monthly: 6
Keep Yearly: 1Configure 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.
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
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
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, orcancelled) - Last checked time
- Compression statistics and snapshot count
- Doctor report output
Next Steps
Now that you have a working backup, consider:
<Card title="Set Up Cloud Storage" icon={
Configure offsite backups to S3, Google Cloud, or Azure
<Card title="Optimize Retention" icon={
Fine-tune retention policies to balance storage and history
Common Tasks
Restoring Data
To restore data from a backup:
Navigate to 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
Pausing a Backup Job
To temporarily stop a backup from running:
- Navigate to the backup job
- Toggle the Enabled switch to off
- The job will skip scheduled runs until re-enabled
Checking Backup Details
To inspect a backup run:
- Click on a backup job
- Review the current Status, Last backup, and Snapshots sections
- Click a snapshot to inspect its file browser and restore actions
- 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:
- Navigate to backup job
- Select the snapshot you want to delete
- Click Delete Snapshot
- Confirm deletion
Troubleshooting
Backup Failed: Permission Denied
Ensure the volume directory has proper permissions:
# On the host system
sudo chmod -R 755 /path/to/your/dataBackup 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:
<Card title="GitHub Issues" icon={
Report bugs or request features at github.com/nicotsx/zerobyte/issues
- 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:
Your data is now protected with encrypted, deduplicated backups. Zerobyte will continue to run backups automatically according to your schedule.
import { GithubLogoIcon } from "@phosphor-icons/react";
import { FolderPlus, Cloud, Bell, History, FileText, BookOpen, Users, Check } from "lucide-react";
import { Tab, Tabs } from "fumadocs-ui/components/tabs";
import { Accordion, Accordions } from "fumadocs-ui/components/accordion";
Apps Docs .Cta.Json (`apps/docs/.cta.json`)
{
"projectName": "docs",
"mode": "file-router",
"typescript": true,
"packageManager": "npm",
"includeExamples": false,
"tailwind": true,
"addOnOptions": {},
"envVarValues": {},
"git": false,
"routerOnly": false,
"version": 1,
"framework": "react",
"chosenAddOns": ["nitro"]
}
Apps Docs Components.Json (`apps/docs/components.json`)
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "base-lyra",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/styles.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "phosphor",
"rtl": false,
"aliases": {
"components": "#/components",
"utils": "#/lib/utils",
"ui": "#/components/ui",
"lib": "#/lib",
"hooks": "#/hooks"
},
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
}
Apps Docs Package.Json (`apps/docs/package.json`)
{
"name": "docs",
"private": true,
"type": "module",
"imports": {
"#/": "./src/"
},
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"deploy": "bun run build && wrangler deploy",
"cf-typegen": "wrangler types",
"tsc": "tsc --noEmit"
},
"dependencies": {
"@base-ui/react": "^1.9.0",
"@fontsource-variable/jetbrains-mono": "^5.3.0",
"@phosphor-icons/react": "^2.1.10",
"@tailwindcss/vite": "^4.3.3",
"@tanstack/react-devtools": "^0.10.13",
"@tanstack/react-router": "^1.170.45",
"@tanstack/react-router-devtools": "^1.167.2",
"@tanstack/react-router-ssr-query": "^1.167.3",
"@tanstack/react-start": "^1.169.3",
"@tanstack/router-plugin": "^1.168.47",
"@types/mdx": "^2.0.14",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"fumadocs-core": "^16.17.5",
"fumadocs-mdx": "^15.4.7",
"fumadocs-ui": "^16.17.5",
"lucide-react": "^1.59.0",
"react": "^19.3.1",
"react-dom": "^19.3.1",
"shadcn": "^4.22.0",
"tailwind-merge": "^3.7.0",
"tailwindcss": "^4.3.3",
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.64.0",
"@tailwindcss/typography": "^0.5.20",
"@tanstack/devtools-vite": "^0.8.5",
"@testing-library/dom": "^10.4.2",
"@testing-library/react": "^16.3.3",
"@types/node": "^26.6.5",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@vitejs/plugin-react": "^6.1.2",
"jsdom": "^30.1.3",
"typescript": "^7.0.2",
"vite": "^8.3.4",
"vite-plugin-killer-instincts": "^1.1.0",
"vitest": "^5.0.5",
"wrangler": "^4.151.1"
},
"pnpm": {
"onlyBuiltDependencies": [
"esbuild",
"lightningcss"
]
}
}
Apps Docs Tsconfig.Json (`apps/docs/tsconfig.json`)
{
"include": ["/*.ts", "/.tsx"],
"compilerOptions": {
"target": "ES2022",
"jsx": "react-jsx",
"module": "ESNext",
"paths": {
"#/": ["./src/"],
"@/": ["./src/"],
"fumadocs-mdx:collections/": ["./.source/*"]
},
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["vite/client"],
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
/* Linting */
"skipLibCheck": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
}}
Apps Docs .Vscode Settings.Json (`apps/docs/.vscode/settings.json`)
{
"files.watcherExclude": {
"/routeTree.gen.ts": true
},
"search.exclude": {
"/routeTree.gen.ts": true
},
"files.readonlyInclude": {
"**/routeTree.gen.ts": true
}
}
Apps Docs Public Manifest.Json (`apps/docs/public/manifest.json`)
{
"short_name": "TanStack App",
"name": "Create TanStack App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
Apps Docs Public Robots (`apps/docs/public/robots.txt`)
https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
Sitemap: https://zerobyte.app/sitemap.xml
Apps Docs Content Docs Cli (`apps/docs/content/docs/cli.mdx`)
title: CLI Reference
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:
docker exec -it zerobyte bun run cli <command>Use --help to see all options:
docker exec -it zerobyte bun run cli --help
docker exec -it zerobyte bun run cli reset-password --helpMost 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
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 defaultFor 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 |
Recommended
| Variable | Description | Default |
|---|---|---|
TZ |
Timezone for the container. Important for accurate backup scheduling. | UTC |
Optional
| Variable | Description | Default |
|---|---|---|
PORT |
Port the web interface and API listen on inside the container. | 4096 |
RESTIC_HOSTNAME |
Hostname used by Restic when creating snapshots. Automatically detected if a custom hostname is set in Docker. | zerobyte |
GOMAXPROCS |
Optional positive integer passed to Restic processes to limit CPU scheduler threads. See Performance tuning. | 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:
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.
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.txtGenerate the secret file with:
mkdir -p ./secrets
openssl rand -hex 32 > ./secrets/app_secret.txtDo 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:
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/zerobytevolumes:
- /mnt/tank/docker/zerobyte:/var/lib/zerobyteAdditional 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:
cap_add:
- SYS_ADMIN
devices:
- /dev/fuse:/dev/fuseSimplified Installation (local directories only)
If you only need local directory backups, no special capabilities are required:
# No cap_add or devices needed
ports:
- "4096:4096"Port Configuration
By default, Zerobyte listens on port 4096:
ports:
- "4096:4096"To bind to localhost only (recommended when using a reverse proxy):
ports:
- "127.0.0.1:4096:4096"Cookie Security
The BASE_URL determines how authentication cookies behave:
| BASE_URL | Cookie Behavior |
|---|---|
http://192.168.1.50:4096 |
Secure cookies disabled, allows login over HTTP |
http://localhost:4096 |
Secure cookies disabled, allows local development |
https://zerobyte.example.com |
Secure cookies enabled, requires HTTPS |
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.
Example with Docker Secrets
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.txtIn 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:
docker compose pull
docker compose up -d
docker compose logs -f zerobyteimport { 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:
docker --version
docker compose versionBasic 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:
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/zerobyteRemote 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:
openssl rand -hex 32Example Configuration:
environment:
- TZ=America/New_York
- BASE_URL=http://192.168.1.100:4096
- APP_SECRET=a1b2c3d4e5f6... # Output from openssl commandRequired 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 |
- 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:
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/lib/zerobyte:/var/lib/zerobytevolumes:
- /etc/localtime:/etc/localtime:ro
- /mnt/tank/docker/zerobyte:/var/lib/zerobyteThis ensures your configuration, encryption keys, and database persist across upgrades.
4. Start Zerobyte
Start the container using Docker Compose:
docker compose up -dVerify the container is running:
docker compose ps
docker compose logs -f zerobyte5. Access the Web Interface
Once the container is running, access Zerobyte at the URL you specified in BASE_URL:
http://<your-server-ip>:4096Simplified 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:
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:/mydataTrade-offs:
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:
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:/mediaAfter adding volume mounts, restart the container:
docker compose down
docker compose up -dThe 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:
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.txtWhen authoring a provisioning file, you can reference these secrets:
env://S3_SECRET_KEY- Resolves from environment variableS3_SECRET_KEYfile://smb_password- Resolves from/run/secrets/smb_password
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:
curl https://rclone.org/install.sh | sudo bash
rclone configMount Config into Container
Update your compose.yaml:
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/lib/zerobyte:/var/lib/zerobyte
- ~/.config/rclone:/root/.config/rclone:roRestart Container
docker compose down && docker compose up -dYour 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:
<Card title="Configuration Reference" icon={
All environment variables and Docker settings
<Card title="Troubleshooting" icon={
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.
Key Features
<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.
Use Cases
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
<Card title="Quick Start" icon={
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:
environment:
- LOG_LEVEL=debugThen view logs:
docker logs -f zerobyteContainer Won't Start
- Check logs:
docker compose logs zerobyte - Verify exactly one of
APP_SECRETorAPP_SECRET_FILEis set - If using
APP_SECRET_FILE, ensure the file exists inside the container, is readable, and contains a 32-256 character secret - Ensure
/var/lib/zerobyteexists and has correct permissions - Verify port 4096 is not already in use:
netstat -tuln | grep 4096
Permission Issues
If you encounter permission errors on the data directory:
sudo chown -R 1000:1000 /var/lib/zerobyteRemote 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_ADMINcapability:yamlcap_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:
# Mount on host
sudo mount -t cifs //server/share /mnt/your-remote-share -o credentials=/path/to/creds# Then in compose.yaml
volumes:
- /mnt/your-remote-share:/dataSecurity 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:
sudo aa-status
docker inspect --format='{{.AppArmorProfile}}' zerobyteIf it returns docker-default, disable AppArmor for the container:
security_opt:
- apparmor:unconfinedDocker's default seccomp profile may block mount-related syscalls:
security_opt:
- seccomp:unconfinedAdjust the SELinux context:
security_opt:
- label:type:container_runtime_tOr disable SELinux enforcement for the container:
security_opt:
- label:disableFUSE Mount Failures
FUSE-based mounts (SFTP volumes, rclone volumes) require /dev/fuse:
devices:
- /dev/fuse:/dev/fuseVerify FUSE is accessible inside the container:
docker exec zerobyte ls -la /dev/fuseRclone Issues
Test on Host First
Before reporting rclone issues, verify rclone works on your Docker host:
# List configured remotes
rclone listremotes
# Test listing a remote
rclone lsd myremote:
# Test reading files
rclone ls myremote:path/to/testIf these commands fail on the host, fix your rclone configuration first. Common causes:
- Expired OAuth tokens, run
rclone configto 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:
docker exec zerobyte ls -la /root/.config/rclone/Ensure the config is mounted:
volumes:
- ~/.config/rclone:/root/.config/rclone:roFor non-root containers, set the correct path:
environment:
- RCLONE_CONFIG_DIR=/home/appuser/.config/rclone
volumes:
- ~/.config/rclone:/home/appuser/.config/rclone:roRestart the container after mounting the config.
"Failed to Create File System" Error
Usually an authentication failure. On your host:
- Run
rclone configand re-authenticate the remote - Verify with
rclone lsd remote: - 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_peminstead ofkey_file - Use ssh-agent: Set
key_use_agent = trueand 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/fusedevice mountedSYS_ADMINcapability
"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=1orGOMAXPROCS=2on the Zerobyte container and restart - Change repository compression mode from
maxtoautooroff - Add
--read-concurrency 1to 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:
sudo chmod -R 755 /path/to/your/dataBackup Running Slowly
- Check network bandwidth (for cloud repositories)
- Check disk I/O performance
- Enable compression for large compressible files
- Try
--read-concurrency 4or--read-concurrency 8for 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.
Getting Help
If you've tried the solutions above and still have issues:
- Enable debug logging (
LOG_LEVEL=debug) - Collect relevant logs:
docker compose logs --tail=200 zerobyte - 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, orCustom (Cron) - Retention policy, optionally define how many snapshots to keep
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 |
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
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 filesnode_modules/, dependency directories.cache/, cache directories*.log, log files
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.
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:
Navigate to the backup job
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.
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 onlyconfiguration 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
Next steps
<Card title="Troubleshooting" icon={
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. |
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.
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:
memberadminowner
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.
Related docs
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.
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.
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.
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.
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:
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.
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.
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.
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 provisioningfile://secret_name, resolves the value from a Docker secret file at/run/secrets/secret_nameduring 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
Next steps
<Card title="Rclone integration" icon={
Set up rclone to access 40+ cloud storage backends
<Card title="Configuration reference" icon={
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.
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)
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>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>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>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>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>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.
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 yourcompose.yamlduring provisioningfile://secret_name, resolves the value from a Docker secrets file at/run/secrets/secret_nameduring 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.
Best practices
- `production-database-dumps`
- `customer-uploads-nfs`
- `accounting-smb-share`
Avoid generic names like `volume1` or `backup`, they become confusing as you add more volumes.- Live production systems
- Archive or compliance data
- Shared storage used by multiple applicationsNext steps
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 |
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
Related docs
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.
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:
{
"phase": "pre",
"event": "backup.pre",
"jobId": "job_...",
"scheduleId": "sched_...",
"organizationId": "org_...",
"sourcePath": "data"
}Post-backup webhook example:
{
"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:
X-Zerobyte-Hook-Secret: replace-with-a-long-random-secret
Content-Type: application/jsonHeader 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
- Add the webhook origin to
WEBHOOK_ALLOWED_ORIGINSin the Zerobyte environment. - Restart Zerobyte so the environment change is loaded.
- Open Backups and select the backup job.
- Edit the job and expand Advanced.
- Fill Pre-backup webhook or Post-backup webhook.
- Add any required headers.
- Leave the body empty unless the receiving service requires a custom payload.
- 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:
services:
zerobyte:
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
- WEBHOOK_ALLOWED_ORIGINS=http://host.docker.internal:9000How-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
postgrescontainer. - Restic backs up the mounted data.
- Post-backup hook starts the
postgrescontainer again.
1. Install webhook on the Docker host
On Debian or Ubuntu:
sudo apt-get update
sudo apt-get install webhookwebhook 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:
sudo mkdir -p /opt/zerobyte-hooksCreate /opt/zerobyte-hooks/stop-postgres.sh:
#!/bin/sh
set -eu
CONTAINER=postgres
STATE=$(docker inspect -f '{{.State.Running}}' "$CONTAINER")
if [ "$STATE" = "true" ]; then
docker stop "$CONTAINER"
fiCreate /opt/zerobyte-hooks/start-postgres.sh:
#!/bin/sh
set -eu
CONTAINER=postgres
STATE=$(docker inspect -f '{{.State.Running}}' "$CONTAINER")
if [ "$STATE" != "true" ]; then
docker start "$CONTAINER"
fiMake both scripts executable:
sudo chmod +x /opt/zerobyte-hooks/stop-postgres.sh /opt/zerobyte-hooks/start-postgres.shIf your container has a different name, change CONTAINER=postgres in both scripts.
3. Create the webhook config
Create /opt/zerobyte-hooks/hooks.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:
sudo webhook -hooks /opt/zerobyte-hooks/hooks.json -port 9000 -verbose -http-methods POSTIn another shell, test both hooks:
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-postgresOnce 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:
services:
zerobyte:
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
- WEBHOOK_ALLOWED_ORIGINS=http://host.docker.internal:9000Restart Zerobyte:
docker compose up -d6. Add the hooks to the backup job
Open the backup job in Zerobyte, edit it, and expand Advanced.
Use these values:
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-secretLeave 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:
[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.targetEnable and start it:
sudo systemctl daemon-reload
sudo systemctl enable --now zerobyte-webhook.service
sudo systemctl status zerobyte-webhook.serviceCheck logs with:
sudo journalctl -u zerobyte-webhook.service -fimport { 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.
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:
- Zerobyte mounts the volume inside the Linux container.
- Linux sees that mounted volume as a normal filesystem.
- 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:
- Run Zerobyte close to the data and back it up as a local directory there.
- Mount the source on the host first, then bind-mount that local path into Zerobyte as a Directory volume.
- Use remote mounted backends mainly for content backup, not for exact replication.
Practical advice
- If you mainly care about recovering files, most mounted backends are fine.
- If you care a lot about permissions too, prefer Directory first.
- After Directory, NFS is often the next best choice.
- SMB can also be a good option, especially for Windows and Samba shares, but it is worth testing first.
- Treat WebDAV, SFTP, and rclone as content-first backends.
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:
- A destination is a reusable delivery channel such as Slack, SMTP, ntfy, or a webhook.
- 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, andfailure.
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 |
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
- Open Notifications in the sidebar.
- Click Create Destination.
- Pick the notification type.
- Fill in the type-specific settings.
- Save the destination.
- 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
- Open the backup schedule you want to configure.
- In the Notifications section, click Add notification.
- Select an existing destination.
- Choose which events should trigger that destination.
- 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",
]}
**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
```**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:
```**Fields**
- `Webhook URL`
- `Bot Username` (optional)
- `Avatar URL` (optional)
- `Thread ID` (optional)**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`.**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
```**Fields**
- `User Key`
- `API Token`
- `Devices` (optional)
- `Priority` as `-1`, `0`, or `1`**Fields**
- `Bot Token`
- `Chat ID`
- `Thread ID` (optional)
**Example**
```text
Type: Telegram
Bot Token: 123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
Chat ID: -1231234567890
Thread ID: 3
```**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
```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 |
Related docs
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.
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, andprofilescopes.
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:
${BASE_URL}/api/auth/sso/callback/${providerId}Example:
https://backup.example.com/api/auth/sso/callback/acme-oidcSetup checklist
- In your identity provider, create a confidential or web OIDC client for Zerobyte.
- Register the callback URL above as an allowed redirect URI.
- Make sure the provider returns
emailand an email-verification claim. - In Zerobyte, open Settings > Organization > Single Sign-On and click Register new.
- Fill in the provider details and save.
- Decide whether
Link matching emails to existing accountsshould be enabled for this provider. - 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.
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.
Common failure cases
| What the user sees | Typical cause | What to do |
|---|---|---|
Access is invite-only. Ask an organization admin to send you an invitation before signing in with SSO. |
No pending invitation, invitation expired, invitation was cancelled, or the user was removed after an earlier accepted invite | Create a new pending invitation or restore membership before retrying. |
SSO sign-in was blocked because this email already belongs to another user in this instance. |
Zerobyte found an existing account with the same email, but that account is not eligible for auto-linking with this provider | Confirm whether the account already belongs to the organization and whether auto-linking should be enabled on this trusted provider. |
Your identity provider did not mark your email as verified. |
The IdP did not send a verified email claim | Fix email verification in the IdP before retrying. |
You have been banned from this application. |
The user is banned at the application level | Remove the ban or use a different account. |
SSO authentication failed. Please try again. |
Generic provider or callback failure | Check IdP redirect URI, client credentials, issuer, and discovery endpoint. |
Related docs
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:
--read-concurrency 1
--exclude-larger-than 10G
--no-scanExample faster schedule for a local NVMe source:
--read-concurrency 8Limit 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:
services:
zerobyte:
environment:
- GOMAXPROCS=2Then restart Zerobyte:
docker compose up -dUse 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:
--pack-size 64Do 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.
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:
--read-concurrency 1Use higher values only when the source can keep up:
--read-concurrency 4If 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:
--no-scanThis 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:
- Set
GOMAXPROCS=1orGOMAXPROCS=2on the Zerobyte container. - Use repository compression mode
autofor mixed data, orofffor mostly compressed media. - Add
--read-concurrency 1to heavy backup schedules. - Keep the Restic cache on local storage.
- If the repository creates too many small objects or files, test
--pack-size 32on one schedule.
For S3-compatible storage, rclone remotes, and other object stores where each uploaded object has overhead:
- Keep compression at
autounless CPU is clearly the bottleneck. - Test
--pack-size 64on one backup schedule. - If object count is still a problem and uploads are reliable, test
--pack-size 128. - Use repository upload/download limits if the link is shared with other services.
- Avoid
--no-cache; metadata round trips are often the slow part on remote repositories.
Troubleshooting
For high CPU usage, use this order:
- Check whether compression mode is
max. If yes, switch toautooroffand run the next backup. - Set
GOMAXPROCS=2on the Zerobyte container and restart. - If the source disk is also busy, add
--read-concurrency 1to the affected backup schedule. - If the repository is remote and uploads saturate the host, add a repository upload limit.
- 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:
- Make sure the source and repository are not on the same slow disk.
- Use compression mode
autobefore tryingmax. - If the source is fast SSD/NVMe storage, try
--read-concurrency 4. - If the bottleneck is network upload, keep compression enabled and remove upload limits during a test window.
- 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://andfile:// - 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
Prerequisites
- A running Zerobyte instance with an organization ID
- The
PROVISIONING_PATHenvironment variable pointing to your JSON file
Find your organization ID
- Sign in to Zerobyte and switch to the organization you want to provision into
- Open Settings
- Open the Organization tab
- Copy the read-only Organization ID value from Organization Details
Quick Start
Create the provisioning file
Create a provisioning.json file:
{
"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:
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/documentsStart or restart Zerobyte
docker compose up -dOn 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:
{
"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:
{
"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:
{
"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 |
```json
{
"backend": "local",
"path": "/var/lib/zerobyte/repositories/primary",
"isExistingRepository": false
}
``````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
}
``````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
}
``````json
{
"backend": "gcs",
"bucket": "zerobyte-backups",
"projectId": "my-gcp-project",
"credentialsJson": "file://gcs_credentials_json",
"isExistingRepository": true
}
``````json
{
"backend": "azure",
"container": "zerobyte-backups",
"accountName": "storageaccount",
"accountKey": "env://AZURE_STORAGE_ACCOUNT_KEY",
"endpointSuffix": "core.windows.net",
"isExistingRepository": true
}
``````json
{
"backend": "rclone",
"remote": "remote-name",
"path": "zerobyte/backups",
"isExistingRepository": true
}
``````json
{
"backend": "rest",
"url": "https://rest-server.example.com",
"username": "env://REST_USERNAME",
"password": "file://rest_password",
"path": "zerobyte",
"isExistingRepository": true
}
``````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:
{
"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 |
```json
{
"backend": "directory",
"path": "/data/documents"
}
``````json
{
"backend": "nfs",
"server": "10.0.0.10",
"exportPath": "/exports/media",
"port": 2049,
"version": "4.1",
"readOnly": true
}
``````json
{
"backend": "smb",
"server": "fileserver.local",
"share": "team",
"username": "backup-user",
"password": "env://SMB_PASSWORD",
"vers": "3.0",
"domain": "WORKGROUP",
"port": 445,
"readOnly": false
}
``````json
{
"backend": "webdav",
"server": "cloud.example.com",
"path": "/team-a",
"username": "team-a",
"password": "env://WEBDAV_PASSWORD",
"port": 443,
"ssl": true
}
``````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..."
}
``````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 |
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,secretAccessKeyr2:accessKeyId,secretAccessKeygcs:credentialsJsonazure:accountKeyrest:username,passwordsftp:privateKey
Volume fields that support secret references
smb:passwordwebdav:passwordsftp:password,privateKey
Resolved secret values are encrypted before Zerobyte stores them in the database.
Rotating Secrets
To rotate a provisioned secret:
- Update the environment variable or secret file
- Restart Zerobyte with
docker compose restart
Zerobyte re-resolves supported secret references on each startup.
Updating And Removing Managed Resources
- Keep the same
idto update a managed resource in place - Changing only
namekeeps the same underlying resource record - Setting
"delete": trueremoves 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:
{
"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:
backendis not a valid value for that resource typeconfig.backenddoes not match the top-levelbackend- the
configobject does not match the required fields for that backend
Secret reference errors
env://NAMErequires the environment variable to exist in the Zerobyte containerfile://namerequires/run/secrets/nameto exist- Zerobyte stops syncing when it hits a provisioning error, so fix the first reported error and restart again
Important Notes
- Changes to
provisioning.jsononly 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:
curl https://rclone.org/install.sh | sudo bashAlternatively, visit rclone.org/install for platform-specific packages.
Configure a remote
Run the interactive configuration wizard:
rclone configFollow 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:
rclone listremotesTest the connection
List the top-level directories on your remote to confirm everything works:
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:
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/lib/zerobyte:/var/lib/zerobyte
- ~/.config/rclone:/root/.config/rclone:roThen restart the container to pick up the change:
docker compose down && docker compose up -denvironment:
- RCLONE_CONFIG_DIR=/home/appuser/.config/rclone
volumes:
- ~/.config/rclone:/home/appuser/.config/rclone:roUsing 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.
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:
cap_add:
- SYS_ADMIN
devices:
- /dev/fuse:/dev/fuseCreate 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.
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:
volumes:
- ~/.ssh:/root/.ssh:roMake 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:
environment:
- SSH_AUTH_SOCK=/ssh-agent
volumes:
- ${SSH_AUTH_SOCK}:/ssh-agentThis 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:
rclone config reconnect myremote:Restart the container
After refreshing the token, restart the Zerobyte container so it picks up the updated config:
docker compose restartTroubleshooting
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.confexists on the host. - Confirm the mount path in
compose.yamlmatches the expected location inside the container (/root/.config/rcloneby default, or the path set inRCLONE_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.yamlincludescap_add: [SYS_ADMIN]anddevices: [/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/fuseexists on the host:ls -la /dev/fuse.
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
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.passfile - 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
adminorowner - 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
.zbexfile - 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.
How Zerobyte decides which password to use
Zerobyte's runtime password selection is simple:
- If the repository is imported and has a stored custom password, use that custom password.
- 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_SECRETand the recovery key as separate operational secrets. Losing one is not the same as losing the other. - Keep each configuration export passphrase with the same care as the export itself; neither can be recovered from the other.
Related docs
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";
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/resticor/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:
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
- Open Settings, choose your organization, and find Machines.
- Click Connect machine and give the machine a name.
- 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:
sudo zerobyte-agent folders addUse 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:
sudo zerobyte-agent folders listZerobyte 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:
sudo zerobyte-agent status
sudo zerobyte-agent logs --followPress 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:
curl -fsSL https://zerobyte.app/install.sh | sudo bashTo install a specific release, set ZEROBYTE_AGENT_VERSION:
curl -fsSL https://zerobyte.app/install.sh | sudo env ZEROBYTE_AGENT_VERSION=vX.Y.Z bashUpdates 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:
- Sets the repository status to
doctor - Acquires an exclusive repository lock inside the app
- Runs an unlock step
- Runs
restic check - Runs
restic repair-indexonly if the check output suggests that repair - Saves the step-by-step result and updates the final status to
healthy,error, orcancelled
Because Doctor holds an exclusive repository lock, it can block other repository operations such as backups, restores, and snapshot mutations while it runs.
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.
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:
unlockcheckrepair_indexwhen 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
- Use Unlock.
- Retry the blocked backup or restore.
- If problems continue, run Doctor.
Status is `unknown`
- Check whether you recently edited repository settings.
- Use the stats refresh button if you only need updated size numbers.
- Run Doctor if you want a fresh maintenance verdict.
Status is `error`
- Read Last Error.
- Expand the Doctor Report and identify the failing step.
- Fix the real problem first, such as credentials, backend reachability, or repository corruption.
- Run Doctor again after the fix.
Related docs
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.
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
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:
lchown /path/to/file: operation not permitted
lchown /path/to/file: permission denied
chmod /path/to/file: operation not permitted
failed to restore timestampthe 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.
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:
- mount the remote share on the host
- bind-mount that host path into the Zerobyte container
- restore to that bind-mounted path or to another local staging path first
This often behaves better than restoring straight through a FUSE-based mount created inside the container.
Use a filesystem that supports POSIX metadata when exact restore fidelity matters
If you need the restored data to keep Unix owner, group, and mode information exactly, restore to a destination that supports those semantics end-to-end.
That is usually a local Linux filesystem or a properly configured NFS share, not a translated or appliance-managed mount.
Practical recommendations
- Test restores regularly, not just backups
- Prefer a custom restore path for routine verification
- Keep at least one fast local repository for quick recovery
- Use Download for ad-hoc recovery to your own machine
- Verify recovered files before deleting or overwriting the originals
Related guides
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:
environment:
- BASE_URL=https://zerobyte.example.com
- TRUST_PROXY=trueTRUST_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:
docker compose down && docker compose up -dHow BASE_URL affects cookie security
Zerobyte uses BASE_URL to determine whether authentication cookies should be marked as Secure. This directly affects how login sessions work:
| BASE_URL value | Cookie behavior |
|---|---|
http:// or IP address (e.g., http://192.168.1.100:4096) |
Secure cookies disabled, cookies sent over HTTP. Suitable for local network access. |
https:// with domain (e.g., https://zerobyte.example.com) |
Secure cookies enabled, cookies only sent over HTTPS. Required for production. |
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:
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:
sudo nginx -t && sudo systemctl reload nginxCaddy
Caddy automatically provisions and renews TLS certificates from Let's Encrypt. Add this to your Caddyfile:
zerobyte.example.com {
reverse_proxy localhost:4096
}Reload Caddy after saving:
sudo systemctl reload caddyCaddy 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:
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/zerobyteMake 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:
ports:
- "127.0.0.1:4096:4096"This ensures all traffic goes through your reverse proxy, which handles TLS and authentication headers.
Security considerations
- 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 withopenssl 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.
# 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.comVerifying 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:
docker compose logs -f zerobyteConfirm 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
- Docker and Docker Compose
- A Tailscale account and an auth key
Setup
This example uses a sidecar networking pattern where Zerobyte shares the Tailscale container's network namespace.
Create 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/zerobyteCreate .env file
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=falseStart the stack
docker compose up -dVerify 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:
- Set
TS_USERSPACE=truein your.env - Remove the
devices: /dev/net/tunsection 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/tunexists 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:backupand configure ACLs in Tailscale admin.
Verify the tailnet address:
docker exec zerobyte-tailscale tailscale status
docker exec zerobyte-tailscale tailscale ip -4import { Step, Steps } from "fumadocs-ui/components/steps";
--- METRICS ---
- Files Extracted: 45
- Estimated Token Budget: ~67550 tokens
- Canonical Reference: https://codewiki.google/github.com/nicotsx/zerobyte