Mount an S3 Bucket as a Filesystem on a VPS with ZeroFS & JuiceFS

Turn a Bunny.net S3 bucket into a real POSIX filesystem on your VPS. Step-by-step setup for ZeroFS and JuiceFS, with caching, systemd services, and which one to pick.

Mount an S3 Bucket as a Filesystem on a VPS with ZeroFS & JuiceFS

Object storage is cheap. A VPS with a big local disk is not. So the obvious move is to keep your bulk data in an S3 bucket and only pay for the storage you actually use, while your server stays small and fast. The catch is that S3 speaks HTTP, not POSIX, so most apps can’t write to it like a normal folder.

That gap is what filesystem layers solve. They sit between your applications and the bucket, presenting a regular mount point like /mnt/data while shuffling bytes to and from object storage behind the scenes. I’ve been running this setup on a few servers, and Bunny.net adding an S3-compatible API to their storage made it a lot more attractive, mostly because their storage is $0.01/GB with no egress fees through their CDN.

This guide walks through two tools that do the job well: ZeroFS and JuiceFS. Both turn an S3 bucket into a mountable filesystem on a VPS, but they take very different paths to get there. I’ll show you how to set up each one against a Bunny.net bucket, then help you pick.

Try Bunny.net free for 14 days

You’ll need an S3-compatible bucket for this guide. Sign up at Bunny.net with no credit card and get a 14-day trial. My full Bunny.net review covers the rest of the platform.

Why mount S3 as a filesystem

Before the how, here’s the why. Pointing a filesystem at object storage gets you a few things a plain VPS disk can’t:

  • Cheap, elastic capacity: Pay per GB instead of resizing a volume every time you run low
  • Off-server durability: Files live in replicated object storage, not on one disk that can die
  • Shared storage: Multiple servers can read from the same bucket-backed mount
  • No app changes: Software writes to a path, not an SDK, so existing tools just work
  • Easy backups and media: Great for media libraries, backups, archives, and static assets

It’s not magic, though. S3 round trips take 50-300ms, so without local caching, a naive mount feels painfully slow for small files. Both tools below solve this with a local cache, which is the main reason to use them over something basic like s3fs.

The two approaches compared

ZeroFS and JuiceFS reach the same destination through different architectures. This matters for setup and for which workloads each one handles well.

ZeroFSJuiceFS
LanguageRustGo
MetadataStored in the bucket (LSM tree)Separate database (Redis, etc.)
Extra servicesNone neededNeeds a metadata engine
ProtocolsNFS, 9P, NBDFUSE mount, S3 gateway
EncryptionAlways on (XChaCha20)Optional
Compressionzstd / LZ4LZ4 / zstd
Block devicesYes (NBD, runs ZFS)No
Setup effortLow (single binary)Medium (binary + database)
Best forSimple single-node mounts, block storageShared/multi-client, large scale
LicenseAGPL-3.0 / commercialApache 2.0

The short version: ZeroFS is simpler because it stores everything (including metadata) in the bucket, so there’s no database to run. JuiceFS is more battle-tested at scale and lets many clients share one filesystem, but it needs a metadata engine like Redis alongside the object storage.

A note on metadata

JuiceFS keeps file metadata (names, sizes, directory structure) in a separate database for speed and consistency. That database is critical, lose it and you lose the map to your data. ZeroFS sidesteps this by keeping metadata in the bucket itself as a log-structured merge tree.

Step 1: Create a Bunny.net S3 bucket

Both tools need an S3-compatible bucket. Here’s how to set one up on Bunny.net.

S3 compatibility is set at creation

Bunny’s S3 API is still in beta and must be enabled when you create the storage zone. You can’t toggle it on an existing zone, so create a fresh one for this.

  1. In the Bunny dashboard, go to StorageAdd Storage Zone
  2. Give it a name (4+ characters, letters, numbers, and dashes only). This name becomes your bucket name and your access key
  3. Enable the S3 Compatibility option
  4. Pick a storage tier and a region close to your VPS
  5. Turn on replication for at least one extra region (recommended for durability)
  6. Confirm and add the zone

Once it’s created, open the Access tab and grab your credentials. You’ll map them to standard S3 values like this:

S3 settingBunny value
Access Key IDYour storage zone name
Secret Access KeyYour storage zone password
Endpointhttps://[region]-s3.storage.bunnycdn.com
Regionde, ny, sg, uk, se, la, or jh

S3 compatibility currently works in Frankfurt (de), New York (ny), Singapore (sg), London (uk), Stockholm (se), Los Angeles (la), and Johannesburg (jh). Bunny only supports path-style URLs (endpoint/bucket-name/key), which both tools handle fine.

You can test the credentials with the AWS CLI before going further:

aws s3 ls s3://your-zone-name/ \
  --endpoint-url https://de-s3.storage.bunnycdn.com

If you want a deeper look at how Bunny Storage compares on price, I wrote a Bunny Storage vs S3 vs Backblaze breakdown.

Prerequisites

For either tool you’ll want:

  • A Linux VPS (Ubuntu/Debian works well here)
  • Root or sudo access
  • A local SSD with some free space for the cache (10 GB+ is a good start)
  • The Bunny S3 credentials from above
Hetzner VPS Hostinger VPS DigitalOcean $100 Free

If you’re new to running services on a VPS, my Docker on Ubuntu guide and adding a new drive with LVM cover the basics around storage and self-hosting.

Option A: ZeroFS

ZeroFS is a Rust tool that serves an S3 bucket as a POSIX filesystem over NFS and 9P, or as a raw block device over NBD. Everything runs in a single userspace process, and data is always compressed and encrypted before it leaves your server. There’s no separate database, which makes it the simpler of the two to stand up.

Install ZeroFS

The install script grabs the right prebuilt binary for your platform:

curl -sSfL https://sh.zerofs.net | sh

To pin a version and install without root:

curl -sSfL https://sh.zerofs.net | VERSION=v1.2.6 INSTALL_DIR=$HOME/.local/bin sh

CPU requirement

The prebuilt Linux amd64 binary needs a CPU with AVX2 (Intel Haswell 2013+ or AMD Excavator 2015+). On older hardware the binary exits with an illegal-instruction error, and you’ll need to build from source for a baseline x86-64 target.

Configure ZeroFS

Generate a starter config and edit it:

zerofs init   # writes zerofs.toml

Here’s a working zerofs.toml for a Bunny.net bucket. Note the conditional_put line, which I’ll explain below:

[cache]
dir = "/var/cache/zerofs"
disk_size_gb = 10.0
memory_size_gb = 1.0

[storage]
url = "s3://your-zone-name/zerofs-data"
encryption_password = "${ZEROFS_PASSWORD}"

[filesystem]
compression = "zstd-3"

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

[aws]
access_key_id = "${AWS_ACCESS_KEY_ID}"
secret_access_key = "${AWS_SECRET_ACCESS_KEY}"
endpoint = "https://de-s3.storage.bunnycdn.com"
default_region = "de"
conditional_put = "redis://localhost:6379"

Why the Redis line

ZeroFS needs conditional writes (put-if-not-exists) to fence against corruption. AWS S3 supports this natively, but many S3-compatible stores don’t yet expose it. Setting conditional_put to a Redis URL lets ZeroFS coordinate those writes through Redis instead. Install Redis with sudo apt install redis-server and keep it on localhost. If you confirm your Bunny zone handles conditional puts, you can drop this line.

Bunny.net S3 compatibility issue with ZeroFS

As of mid-2026, Bunny’s S3 API (still in closed preview) does not return an ETag header on PUT responses. ZeroFS requires ETags to verify writes, so startup fails with a MissingEtag error even with conditional_put configured. This is a Bunny-side limitation they’ll likely fix as the API matures. Until then, use JuiceFS (Option B) with Bunny, or pair ZeroFS with a different S3 provider like Cloudflare R2 or AWS S3.

The encryption_password is not optional, ZeroFS has no unencrypted mode. Store it safely, because losing it means losing access to everything in the bucket.

Run ZeroFS

Export your secrets and start the server:

export AWS_ACCESS_KEY_ID="your-zone-name"
export AWS_SECRET_ACCESS_KEY="your-storage-password"
export ZEROFS_PASSWORD="a-long-random-passphrase"

zerofs run -c zerofs.toml

Mount the filesystem

ZeroFS is now serving NFS on 127.0.0.1:2049. Mount it like any NFS share:

sudo mkdir -p /mnt/data
sudo mount -t nfs -o nolock,vers=3 127.0.0.1:/ /mnt/data

That’s it. Anything you write to /mnt/data gets compressed, encrypted, and pushed to your Bunny bucket, with hot reads served from the local cache in microseconds.

Run it as a service

For anything beyond testing, run ZeroFS under systemd so it survives reboots. Create /etc/systemd/system/zerofs.service:

[Unit]
Description=ZeroFS S3-backed filesystem
After=network-online.target redis-server.service
Wants=network-online.target

[Service]
Environment=AWS_ACCESS_KEY_ID=your-zone-name
Environment=AWS_SECRET_ACCESS_KEY=your-storage-password
Environment=ZEROFS_PASSWORD=a-long-random-passphrase
ExecStart=/usr/local/bin/zerofs run -c /etc/zerofs/zerofs.toml
Restart=on-failure

[Install]
WantedBy=multi-user.target

Then enable it:

sudo systemctl daemon-reload
sudo systemctl enable --now zerofs

Keep secrets out of the unit file

Putting credentials directly in a systemd unit is fine for a quick start, but for production move them into an EnvironmentFile= with 600 permissions, or use a secrets manager. Never commit these to git.

ZeroFS can also expose the bucket as a raw block device over NBD, which is what makes the “run ZFS on top of S3” demos possible. That’s beyond a basic mount, but it’s there if you need it.

Option B: JuiceFS

JuiceFS is a mature, widely deployed distributed filesystem built on object storage plus a separate metadata engine. It’s fully POSIX-compatible, supports thousands of concurrent clients, and is used in production for big data and machine learning workloads. The tradeoff is that you run a metadata database alongside it.

Install Redis for metadata

JuiceFS stores file metadata in an engine like Redis, MySQL, or SQLite. For a single VPS, Redis is the easy choice. You can install it straight on the host or run it in Docker, whichever fits how you manage the box.

Protect your metadata engine

The Redis database is the index to your entire filesystem. Back it up regularly (enable RDB/AOF persistence) and never expose it to the public internet. If you lose the metadata, the data blocks in your bucket become unusable.

Install JuiceFS

One command pulls the latest client:

curl -sSL https://d.juicefs.com/install | sh -

Check it installed:

juicefs version

Format the filesystem

The format command initializes the filesystem, linking your metadata engine to the Bunny bucket. Run it once:

juicefs format \
  --storage s3 \
  --bucket https://de-s3.storage.bunnycdn.com/your-zone-name \
  --access-key your-zone-name \
  --secret-key your-storage-password \
  redis://localhost:6379/1 \
  myjfs

A few notes on the flags:

  • --bucket uses the path-style URL Bunny requires, with your zone name as the last path segment
  • redis://localhost:6379/1 is the metadata engine (database 1 on local Redis)
  • myjfs is the volume name, used in later commands

Mount the filesystem

Now mount it, pointing at the same Redis URL:

sudo mkdir -p /mnt/data
juicefs mount redis://localhost:6379/1 /mnt/data --background

JuiceFS keeps a local cache (default under /var/jfsCache) so repeat reads stay fast. You can tune the cache size at mount time:

juicefs mount redis://localhost:6379/1 /mnt/data \
  --cache-size 10240 \
  --background

--cache-size is in MB, so 10240 gives you a 10 GB local cache.

Run it as a service

JuiceFS can generate a systemd-friendly mount, but the simplest durable approach is a unit file at /etc/systemd/system/juicefs.service:

[Unit]
Description=JuiceFS mount
After=network-online.target redis-server.service
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/juicefs mount redis://localhost:6379/1 /mnt/data --cache-size 10240
ExecStop=/usr/local/bin/juicefs umount /mnt/data
Restart=on-failure

[Install]
WantedBy=multi-user.target

Enable it:

sudo systemctl daemon-reload
sudo systemctl enable --now juicefs

Check the filesystem

JuiceFS ships handy inspection commands:

juicefs status redis://localhost:6379/1   # show volume info
juicefs bench /mnt/data                   # run a quick benchmark

Performance and caching

Neither tool can beat physics, S3 latency is what it is, so the local cache is what makes the experience usable. A few things worth knowing:

  • Size the cache for your hot set: Give the cache enough room to hold the files you read often. A 10 GB cache on a small VPS covers most media and web workloads
  • Use a fast cache disk: Put the cache on local NVMe, not a network volume, or you defeat the purpose
  • Writes are async: Both tools batch and upload in the background, so write throughput feels local until the cache fills
  • Pick a nearby region: Match your Bunny region to your VPS location to cut round-trip time
  • Mind small-file workloads: Millions of tiny files stress metadata more than bandwidth, where JuiceFS’s dedicated engine has an edge

For workloads that are mostly large sequential reads and writes (media, backups, archives), both tools fly once the cache is warm. For random small-file access, performance depends heavily on cache hit ratio.

Which one should you pick?

After running both, here’s my rule of thumb:

Choose ZeroFS if you want the simplest setup, you’re on a single node, you like that encryption is mandatory, or you need block-device features like running ZFS on top of S3. No extra database to babysit is a real advantage for a small VPS. Pair it with Cloudflare R2 or AWS S3 (Bunny’s S3 API currently doesn’t return ETags, which ZeroFS needs).

Choose JuiceFS if you need multiple servers sharing one filesystem, you’re operating at larger scale, you want the most proven option with the widest community, you already run Redis, or you’re using Bunny.net S3 (JuiceFS works fine with Bunny’s S3 API). The metadata engine is extra work, but it buys you consistency and concurrency that a single-node tool can’t match.

For a home server or a single VPS holding a media library or backups, I lean ZeroFS for the simplicity. For anything shared across machines or headed toward serious scale, JuiceFS is the safer bet.

Troubleshooting

Mount is extremely slow for small files

This is almost always a cold cache or an undersized one. Check that your cache directory is on local NVMe (not a network disk), and bump the cache size so it can hold your frequently accessed files. Also confirm your Bunny region matches your VPS location, a Frankfurt bucket served to a Singapore VPS adds latency to every miss.

ZeroFS errors about conditional puts or fencing

ZeroFS needs put-if-not-exists support, which many S3-compatible stores don’t expose. Set conditional_put = "redis://localhost:6379" in the [aws] section of your zerofs.toml and make sure Redis is running locally. This lets ZeroFS coordinate those writes through Redis instead of the bucket.

JuiceFS mount fails or hangs

Check that Redis is reachable (redis-cli ping should return PONG) and that you’re using the exact same Redis URL you formatted with. A mismatched database number (the /1 at the end) points JuiceFS at an empty metadata store. Also verify the bucket URL is path-style, with your zone name as the last segment.

Permission denied writing to the mount

NFS and FUSE mounts map UIDs/GIDs from the server process. For ZeroFS over NFS, mount with nolock and check ownership of /mnt/data. For JuiceFS, run the mount as the user that needs write access, or adjust the directory permissions after mounting.

Frequently asked questions

Is this faster than just using the disk on my VPS?

No, local disk is always faster for data that fits on it. The point of an S3-backed filesystem is cheap, elastic, off-server capacity for data that doesn’t need disk-speed access: media, backups, archives, and shared storage. The local cache narrows the gap for hot files, but it’s a different tool for a different job.

Can I use this with Docker containers?

Yes. Once the filesystem is mounted at a path like /mnt/data, you can bind-mount that path into containers as a volume. It works well for media servers or backup containers. If you manage stacks with a UI, my Dockge install guide shows how to point compose volumes at any host path.

Does Bunny.net charge for the requests these tools make?

Bunny Storage doesn’t charge per API request, which is a real advantage here since both tools make a lot of small calls. You pay for storage ($0.01/GB single region) and for delivery bandwidth if you serve through the CDN. There are no egress fees when delivering through Bunny CDN. See my Bunny.net review for the full pricing picture.

Will I lose data if the metadata is lost?

For JuiceFS, yes, the Redis metadata engine is the map to your data blocks, so back it up and never run it without persistence. For ZeroFS, metadata lives in the bucket alongside the data, so there’s no separate database to lose, but the encryption password is equally critical: lose it and the data is unrecoverable.

Can I use another S3 provider instead of Bunny?

Absolutely. Both tools work with AWS S3, Cloudflare R2, Backblaze B2, MinIO, and any S3-compatible store. Just swap the endpoint and credentials. I focused on Bunny here because of the flat $0.01/GB pricing and no request fees, which suit the chatty access pattern of a filesystem layer.

Wrapping up

Mounting an S3 bucket as a filesystem used to mean fighting with s3fs and accepting terrible small-file performance. ZeroFS and JuiceFS both fix that with smart local caching, and they make object storage genuinely usable as a working filesystem on a VPS.

Pair either one with Bunny.net’s flat-rate, no-egress storage and you get elastic capacity that costs a fraction of resizing your server’s disk. Start with ZeroFS if you want the least moving parts, reach for JuiceFS when you need shared or large-scale storage.

Try Bunny.net Free for 14 Days