ZeroFS
8 min read

Transforms S3 object storage into a functional filesystem. Exposes file-level access via NFS and 9P, and block-level access via NBD. Always-encrypted with XChaCha20-Poly1305 + LZ4/Zstd compression. No FUSE drivers or kernel modules required.

Built on top of SlateDB — an LSM-tree key-value store — which stores all file metadata and 32KB data chunks as key-value pairs directly in S3 objects.

Architecture

Clients (NFS / 9P / NBD / Web UI)
│
Protocol Servers
│
Virtual Filesystem (VFS)
│
Encryption + Compression
│
Multi-layer Cache (memory → disk → S3)
│
SlateDB (LSM tree)
│
Object Store (S3 / Azure / GCS / local)

Files are broken into 32KB chunks stored as key-value entries in the LSM tree. SlateDB compacts asynchronously, reclaiming space and optimizing reads.

Access Modes

ProtocolUse case
9PRecommended — best POSIX semantics, accurate fsync, Linux/container-native
NFSBroad OS compatibility (Linux, macOS)
NBDRaw block device — run ZFS, ext4, btrfs, etc. directly on S3
Web UIBrowser-based file manager + terminal + live dashboard

Installation

bash
# Install script
curl -sSfL https://sh.zerofs.net | sh

# Via Cargo
cargo install zerofs

# Docker
docker pull ghcr.io/barre/zerofs:latest

Configuration

TOML format. Supports ${ENV_VAR} substitution throughout.

toml
[cache]
dir = "${HOME}/.cache/zerofs" # local cache directory
disk_size_gb = 10.0 # persistent disk cache size
memory_size_gb = 1.0 # optional in-memory block cache

[storage]
url = "s3://bucket/path" # backend URL
encryption_password = "${PASSWORD}" # required

[filesystem]
compression = "zstd-3" # zstd-{1..22} or lz4
max_size_gb = 100.0 # optional quota; default ~16 EiB

[servers.nfs]
addresses = ["127.0.0.1:2049"]

[servers.ninep]
addresses = ["127.0.0.1:5564"]
unix_socket = "/tmp/zerofs.9p.sock" # lower latency than TCP

[servers.nbd]
addresses = ["127.0.0.1:10809"]
unix_socket = "/tmp/zerofs.nbd.sock"

[servers.webui]
addresses = ["127.0.0.1:8080"]
uid = 1000
gid = 1000

[servers.rpc]
addresses = ["127.0.0.1:7000"]
unix_socket = "/tmp/zerofs.rpc.sock" # required for checkpoints & monitoring

[lsm]
wal_enabled = false # enable for fsync-heavy workloads
max_concurrent_compactions = 4

Storage Backends

AWS S3 / S3-compatible:

toml
[storage]
url = "s3://my-bucket/path"

[aws]
access_key_id = "${AWS_ACCESS_KEY_ID}"
secret_access_key = "${AWS_SECRET_ACCESS_KEY}"
endpoint = "https://s3.us-east-1.amazonaws.com"
default_region = "us-east-1"
allow_http = "true" # MinIO only
conditional_put = "redis://localhost:6379" # S3-compatible stores lacking conditional PUT

Azure Blob:

toml
[storage]
url = "azure://container/path"

[azure]
storage_account_name = "${ACCOUNT_NAME}"
storage_account_key = "${ACCOUNT_KEY}"

Google Cloud Storage:

toml
[storage]
url = "gs://my-bucket/path"

[gcp]
service_account = "/path/to/service-account-key.json"
# Or: GOOGLE_APPLICATION_CREDENTIALS env var

Local Filesystem:

toml
[storage]
url = "file:///path/to/storage"

Separate WAL Storage

Route the write-ahead log to a lower-latency store (e.g. local NVMe):

toml
[wal]
url = "file:///mnt/nvme/zerofs-wal"

[wal.aws]
access_key_id = "${WAL_KEY}"
secret_access_key = "${WAL_SECRET}"

CLI

bash
zerofs init # generate default zerofs.toml
zerofs run -c zerofs.toml # start filesystem
zerofs run -c zerofs.toml --read-only # read-only replica
zerofs run -c zerofs.toml --no-compactor # disable background compaction
zerofs run -c zerofs.toml --checkpoint NAME # start from a checkpoint

zerofs checkpoint create -c zerofs.toml NAME # create snapshot
zerofs checkpoint list -c zerofs.toml
zerofs checkpoint info -c zerofs.toml NAME
zerofs checkpoint delete -c zerofs.toml NAME

zerofs monitor -c zerofs.toml # real-time TUI dashboard
zerofs monitor -c zerofs.toml --interval 500 # refresh rate in ms
zerofs compactor -c zerofs.toml # standalone compaction process
zerofs change-password -c zerofs.toml # update encryption key (reads from stdin)

Mounting


bash
# TCP
mount -t 9p -o trans=tcp,port=5564,version=9p2000.L,cache=mmap,access=user 127.0.0.1 /mnt/9p

# Unix socket (lower latency)
mount -t 9p -o trans=unix,version=9p2000.L,cache=mmap,access=user /tmp/zerofs.9p.sock /mnt/9p

9P sends a proper fsync signal to ZeroFS, guaranteeing durability — unlike NFS which buffers writes in memory and reports them as stable.

NFS

bash
# Linux
mount -t nfs -o async,nolock,rsize=1048576,wsize=1048576,tcp,port=2049,mountport=2049,hard 127.0.0.1:/ /mnt

# macOS
mount -t nfs -o async,nolocks,rsize=1048576,wsize=1048576,tcp,port=2049,mountport=2049,hard 127.0.0.1:/ /mnt

NFS writes are reported as stable to clients even when still in memory/cache — trades durability for performance.

NBD

bash
# TCP
nbd-client 127.0.0.1 10809 /dev/nbd0 -N device1 -persist -timeout 600 -connections 4

# Unix socket
nbd-client -unix /tmp/zerofs.nbd.sock /dev/nbd0 -N device1 -persist -timeout 600 -connections 4

NBD devices are stored as files under .nbd/ in any mounted ZeroFS volume. Create a device before connecting:

bash
mount -t nfs 127.0.0.1:/ /mnt/zerofs
truncate -s 100G /mnt/zerofs/.nbd/storage # create 100G block device
umount /mnt/zerofs

Encryption & Security

Always on — cannot be disabled.

LayerDetail
AlgorithmXChaCha20-Poly1305 (authenticated encryption)
Key derivationArgon2id from password → 256-bit DEK
ScopeAll file contents, metadata, permissions, timestamps
GranularityPer SST block (32KB chunks)

What is NOT encrypted (visible in plaintext):

• Per-SST first/last key markers — leaks samples of directory entry names
• SlateDB manifest — SST IDs, segment prefixes, sizes
• S3 object listing — SST blob IDs and counts

The local cache directory contains decrypted, decompressed blocks. Treat it as sensitive data.

Password change:

bash
echo "new-password" | zerofs change-password -c zerofs.toml

Compression

Applied before encryption, reducing S3 bandwidth and storage costs.

OptionTradeoff
zstd-{1..22}Default zstd-3. Higher = better ratio, slower writes
lz4Faster writes, lower compression ratio

Format is auto-detected on read — can be changed anytime without migrating data.

Caching

Three-layer pipeline from hot to cold:

1. Memory cache — in-process block cache (memory_size_gb)
2. Disk cache — persistent, stores decrypted+decompressed blocks (disk_size_gb)
3. S3 — compressed+encrypted SST objects

SQLite microbenchmarks on warm cache:

• Sequential fill: ~19.4 µs/op
• Sequential read: ~0.94 µs/op
• Random read: ~1.6 µs/op

Write-Ahead Log (WAL)

Disabled by default. Enable for workloads with frequent fsyncs (databases, etc.):

toml
[lsm]
wal_enabled = true

Without WAL, each fsync flushes the in-memory memtable to a new SST file, generating many small SSTs. WAL absorbs fsyncs without flushing, reducing compaction churn.

Checkpoints

Named point-in-time snapshots stored in the same S3 bucket. Requires the RPC server to be configured.

bash
zerofs checkpoint create -c zerofs.toml before-migration
zerofs checkpoint list -c zerofs.toml
zerofs run -c zerofs.toml --checkpoint before-migration # restore

Read-Only Replicas

One writer + unlimited read-only readers on the same backend:

bash
zerofs run -c zerofs.toml # read-write
zerofs run -c zerofs.toml --read-only # replica, auto-follows writer, returns EROFS on writes

Standalone Compaction

Decouple compaction from the main process to reduce latency and costs:

bash
zerofs run -c zerofs.toml --no-compactor # main instance
zerofs compactor -c zerofs.toml # separate compactor (run near S3)

Useful for running the compactor on a spot instance co-located with S3 to minimize egress fees.

NBD Advanced Usage

TRIM / Discard

Frees S3 space by deleting LSM chunks for discarded blocks:

bash
fstrim /mnt/block # manual TRIM
mount -o discard /dev/nbd0 /mnt/block # automatic on-write discard
zpool set autotrim=on mypool && zpool trim mypool # ZFS

ZFS on S3

bash
zpool create mypool /dev/nbd0
zpool set autotrim=on mypool

Geo-Distributed ZFS Mirror

Run ZeroFS in different S3 regions, mirror across them with ZFS:

bash
# Connect to 3 regional ZeroFS instances
nbd-client 10.0.1.5 10809 /dev/nbd0 -N storage -persist -timeout 600 -connections 8 # US East
nbd-client 10.0.2.5 10809 /dev/nbd1 -N storage -persist -timeout 600 -connections 8 # EU West
nbd-client 10.0.3.5 10809 /dev/nbd2 -N storage -persist -timeout 600 -connections 8 # AP

zpool create global-pool mirror /dev/nbd0 /dev/nbd1 /dev/nbd2

ZFS L2ARC Tiering

Add local NVMe as L2ARC in front of ZeroFS-backed vdevs:

bash
zpool create mypool /dev/nbd0 /dev/nbd1
zpool add mypool cache /dev/nvme0n1

ZFS manages placement: NVMe for hot data, ZeroFS disk cache for warm, S3 for cold.

Web UI

Configure:

toml
[servers.webui]
addresses = ["127.0.0.1:8080"]
uid = 1000
gid = 1000

Features:

• File manager — drag-and-drop uploads (files and folders), talks to ZeroFS via 9P over WebSocket
• Live dashboard — throughput, IOPS, storage usage, operation counters, GC stats; streams via gRPC-web
• In-browser terminal — runs a Linux VM (v86 emulator) with root filesystem mounted over 9P WebSocket

GitHub Actions

yaml
- uses: Barre/zerofs@main
with:
object-store-url: "s3://bucket/path"
encryption-password: ${{ secrets.ZEROFS_PASSWORD }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

Enables persistent storage and shared artifacts across CI workflow runs.

Performance Benchmarks

All tests: Azure Standard D48lds v6 (48 vCPUs, 96 GiB RAM), Cloudflare R2 backend.

vs JuiceFS (10,000 ops each)

OperationZeroFSJuiceFSRatio
Sequential writes984 ops/s5.6 ops/s175×
Data modifications1,099 ops/s6 ops/s183×
File append1,204 ops/s5.3 ops/s227×

Real-world taskZeroFSJuiceFS
git clone2.6 s34.4 s
TAR extraction8.2 s10m 26s
cargo build3m 4sDNF (>69 min)

JuiceFS also recorded 9,206–9,743 failures per 10,000 operations on modification/append tests (ZeroFS: 0). JuiceFS consumed 239 GB vs ZeroFS 7.6 GB for the same workload (31.6× more storage) and made 898,000 S3 API calls vs ZeroFS's 8,000.

JuiceFS requires a separate metadata database (SQLite/Redis/TiKV). ZeroFS requires only S3.

vs AWS Mountpoint-s3 (100 ops each)

OperationZeroFSMountpoint-s3Ratio
Sequential writes663.87 ops/s0.70 ops/s948×
Write latency1.42 ms1,435 ms1,011×
Empty file creation888.66 ops/s0.09 ops/s9,874×
Directory creation985.98 ops/s2.08 ops/s474×
Random reads1,000.84 ops/s3.20 ops/s313×
Read latency0.90 ms312 ms347×

Real-world taskZeroFSMountpoint-s3
git clone3.1 sfailed
TAR extraction13.5 s~2 hours (est.)
S3 API calls63914,640 (23×)

Mountpoint-s3 maps S3 objects 1:1 to files — it does not support full POSIX semantics or arbitrary writes.

PostgreSQL (pgbench, 50 concurrent clients)

WorkloadTPSLatency
Mixed read/write53,0000.943 ms
Read-only413,0000.121 ms

Filesystem Limits

LimitValue
Max file size16 EiB
Max files (lifetime)2⁶⁴ (~18 quintillion)
Max hard links per file~4 billion (2³²)
Max filesystem size16 EiB (or max_size_gb quota)

Testing & Maturity

• Passes all 8,662 pjdfstest_nfs POSIX compliance tests
• ZFS pool creation, Linux kernel extraction, and zpool scrub verified via CI
• Parallel make -j$(nproc) Linux kernel compilation stress test
• Ubuntu boot environment hosted entirely on S3