Documentation
Set up JuiceMount
From an empty NAS to a volume in Finder. You will confirm your NAS can run the server stack, install it from the release install guide, verify your Mac can reach it, install the menu-bar app, get the Mac approved, and mount. The NAS side is the long part: the install needs release image digests, credentials and storage paths that you fill in by hand. The Mac side is a download and a Setup Assistant.
Self-hosted · macOS 26+ on the Mac · TrueNAS SCALE today, other Docker NAS boxes through the Docker guide · Apache-2.0
step 1 of 8
Check your NAS
JuiceMount has two halves: a server stack that runs on your NAS in Docker, and a menu-bar app on the Mac. Before installing anything, confirm both sides qualify.
- On the Mac
-
- macOS 26.0 or later (the app’s minimum system version). Developed and tested on Apple Silicon; Intel builds are unverified.
- macFUSE, required by the JuiceFS client. The first-run Setup Assistant checks for it and walks you through installing it if it’s missing.
- The pinned JuiceFS engine is bundled with the app. Setup Assistant checks its signature and version; no separate JuiceFS installation is needed. Do not substitute an arbitrary Homebrew
juicefsbinary. - For the easy path, nothing else: the signed app on the GitHub releases page is a download you unzip into
/Applications. To build it yourself you’ll also want Go 1.26+ and Xcode command-line tools (Swift 5.9+), and step 4 explains why an ad-hoc signed build cannot start a mount. - The bundled eject helper, enabled in System Settings → General → Login Items & Extensions before you start a mount. Step 5 explains it.
- On your NAS
-
- TrueNAS SCALE 24.10+ through its Install via YAML screen, or another host with Docker and Docker Compose through the Docker guide (see the cards below).
- Disk for two things: the MinIO object bucket (your actual media) and a small Redis dataset (metadata, AOF-persisted).
- A trusted LAN or private route: the authenticated RESP proxy owns port 30179, Redis itself has no published port, and MinIO serves object data on 30151. Keep them private.
- Network: use a trusted connection to Redis and S3-compatible storage. A slow or interrupted link affects cache misses and pending uploads. For use outside the LAN, supply a trusted private route to the server; step 7 covers it.
Pick your install path
One canonical compose file runs on every host. What differs is how your NAS wants to receive it. TrueNAS SCALE has the release install guide. Every other box, Synology, QNAP and Unraid included, is supported only through the Docker guide.
Fresh 0.5 server installation has not been qualified yet. During the release check, an anonymous pull of the compose-pinned MinIO image failed. Before a fresh install, verify that your NAS can pull every image digest in the release manifest. The release notes list the current limits.
TrueNAS SCALE 24.10+ install guide
The path this project’s QA actually exercises, on TrueNAS SCALE 24.10+ (Electric Eel, Fangtooth, Goldeye). The old “Add Catalog” feature was removed in 24.10, so you can’t sideload third-party catalogs anymore. The replacement is Apps → Discover → ⋮ menu → Install via YAML, which takes a Docker Compose YAML and runs it as a native TrueNAS app.
Have ready before step 2: datasets for the bucket, Redis and the cache, plus two small ones for Manager state and the RESP proxy, created under Datasets → Add Dataset. The install guide has the sizing table.
Any Linux box with Docker + Compose Docker guide
Ubuntu, a laptop with Docker Desktop, an old tower: use the canonical docker-compose.yml with its TrueNAS-flavored bind paths changed for your host, and a private environment file that holds every credential and image digest. The Docker guide has the exact commands.
Have ready: directories on your disks for the bucket, Redis data, and the JuiceFS cache.
Synology DSM 7+ Docker guide only
Synology DSM 7+ is supported only through the Docker path. The repo lists it as a Docker host but does not document Synology’s UI. Render the canonical compose as the Docker guide describes, with the bind paths pointed at folders on your volumes, then run it with Docker Compose or Synology’s Container Manager; consult Synology’s documentation for the exact menu path.
The compose file itself is identical; only the way you feed it to the NAS differs. Nothing here is Synology-tested by this project.
QNAP Docker guide only
Supported only through the Docker path, and not covered by this project’s QA. Render the canonical compose as the Docker guide describes, with the bind paths pointed at shares on your pools, then run it in QNAP’s Container Station; consult QNAP’s documentation for the exact steps.
If you run it on QNAP, a report (success or failure) is a genuinely useful contribution.
Unraid Docker guide only
Supported only through the Docker path, and not covered by this project’s QA. Render the canonical compose as the Docker guide describes, with the bind paths pointed at your array, then run it as a stack through a compose plugin from Community Applications; consult Unraid’s documentation for the exact steps.
As with QNAP, a field report is a real contribution.
Success for this step: you know which path you’re taking and where on your disks the bucket, Redis data, and cache will live.
step 2 of 8
Install the stack
One canonical compose file boots the whole backend: Redis for metadata behind an authenticated proxy, MinIO for file data, a one-shot init that formats the JuiceFS volume, a live JuiceFS mount, the JuiceMount Manager web UI, and a CPU worker. You render it from a private settings file, then hand it to your NAS.
| Service | What it does | Port |
|---|---|---|
redis | JuiceFS metadata authority (AOF persistence), internal only | — |
respproxy | Authenticated Redis gateway for named principals | 30179 |
minio | S3-compatible object store for JuiceFS chunks | 30151 (S3), 30152 (console) |
minio-keys | Provisions separate client and GC object keys | — |
juicefs-init | One-shot pre-flight checks + first-time volume format | — |
juicefs | Live FUSE mount + loopback-only WebDAV | 127.0.0.1:30180 |
juicemount-manager | Control-plane web UI (migrations, trash, backups, maintenance, settings) and Mac enrollment | 30190 |
juicefarm | NAS CPU derivative worker | — |
Watch it come up
Settled. juicefs-init runs once, formats the volume, and exits 0; everything else stays up.
The order matters and compose enforces it: respproxy waits for Redis, minio-keys waits for MinIO, juicefs-init waits for both, and juicefs, the Manager and the worker wait for init to exit 0. On every boot after the first, init detects the already-formatted volume and skips the format.
Follow the install guide
The server install is not copied onto this page, because the release compose changes with each release. Use the guide for your host:
- TrueNAS SCALE: the 0.5 server install guide (
server/INSTALL-TrueNAS.md). You paste the rendered YAML into Apps → Discover → ⋮ → Install via YAML. - Any other Docker host, Synology, QNAP and Unraid included: the Docker guide in
server/README.md, with the same canonical compose and private environment file. - The README quick start covers both halves, server and Mac, in order.
Expect to fill in many values by hand before the compose will render. The guide asks for:
- The four published image digests from the release manifest. Do not replace them with mutable tags.
- A durable Manager key (
JM_ADMIN_KEY) and a MinIO root password. Reuse the same values on every upgrade. - Separate object keys for the client and GC users, and a password for each named Redis proxy principal.
- Host storage paths for the bucket, Redis, the cache, Manager state and the proxy principals file.
- The storage pool’s physical capacity in bytes (on ZFS, from
zpool list -Hp -o size <pool>). On ZFS, also exclude the bucket dataset from recursive snapshots.
The render step refuses to run while any of these is missing or still a placeholder. That is on purpose: it stops an edited form from silently dropping a credential. The shipped compose names the volume zpool; that name becomes your mount point on the Mac, /Volumes/zpool.
What success looks like
docker compose ps shows every long-running service healthy, and the juicefs-init log (docker compose logs juicefs-init) ends with a successful format line on first boot (or reports the volume is already formatted on any boot after that) with no FAIL in it. Format lines are prefixed [format] and every pre-flight line is prefixed [precheck-N] for easy grepping. On TrueNAS the same log appears under Apps → Logs.
step 3 of 8
Verify it’s reachable
Before touching the Mac app, prove two things: the stack is actually up, and your Mac can reach the NAS on the published ports.
The ports your Mac must reach
127.0.0.1 on the NAS, so the Mac does not reach it. Outside the LAN, your private route must carry the required ports.Check it from a browser
From the Mac, with <nas-ip> as your NAS’s LAN IP:
- MinIO console:
http://<nas-ip>:30152should show a login page. - Manager web UI:
http://<nas-ip>:30190should load. Its Principals page,http://<nas-ip>:30190/principals.html, is where you approve the Mac in step 4.
If the init container exits non-zero
Each exit code names the failure. Read the matching [precheck-N] log line for detail.
| Code | Meaning |
|---|---|
| 2 | MinIO unreachable from inside the container network |
| 3 | Redis PING failed |
| 4 | Credentials empty, whitespace, or placeholder pattern: fix the environment file and render again |
| 5 | JM_BUCKET_URL missing the http:// scheme |
| 6 | juicefs format itself errored; read the log line above it for the JuiceFS-side reason |
Security notes before you move on
Redis has no published port. The RESP proxy owns 30179 and refuses connections without a valid principal. The proxy speaks plain RESP: its login crosses the network in cleartext, and enrollment is plain HTTP. Keep 30179 and the Manager on the LAN or a private route, and never publish them to the Internet. The MinIO console (30152) is HTTP-only, so don’t expose it to public IPs. MINIO_ROOT_PASSWORD stays on the server, in the minio and minio-keys services; Macs get the jm-client key, which cannot delete objects. And treat the Manager’s JM_ADMIN_KEY as durable deployment state: the Manager refuses to start if it is empty, shorter than 32 characters, or a placeholder, so reuse the same value on every upgrade.
step 4 of 8
Install the Mac app
Install macFUSE, then download the signed app from GitHub Releases and open it. The app includes its own JuiceFS engine. Prefer to build it yourself? That path is right below.
Download and install
brew install --cask macfuse # approve the system extension if macOS asks
# download JuiceMount.zip and SHA256SUMS from the latest release:
# https://github.com/lelanddutcher/juicemount/releases/latest
# verify the archive against the published checksum, unzip it,
# move JuiceMount.app into /Applications, then:
open /Applications/JuiceMount.app
The release on GitHub is signed, so it opens like any other Mac app: no build step, no xattr, no right-click trick. It is built for Apple Silicon; Intel builds are unverified. Do not install juicefs through Homebrew for JuiceMount: the app bundles its pinned JuiceFS engine, and you should not substitute an arbitrary Homebrew juicefs binary. Upgrading from an earlier version? Read the upgrade notes first and let pending uploads drain.
Build from source (optional)
For contributors. A locally built app may launch without download quarantine, but an ad-hoc signed build cannot Start a mount: the eject helper requires a JuiceMount Developer ID signature and must be enabled. The install script requires a JuiceMount Developer ID signed bundle and refuses installation while the GUI or serving processes are running. Intel builds are unverified. See signing and notarization.
git clone https://github.com/lelanddutcher/juicemount
cd juicemount
./scripts/build-app.sh # Go c-archive + Swift app + codesign
./scripts/install.sh # → /Applications; --launchd is optional
open /Applications/JuiceMount.app
First launch: the Setup Assistant
On first launch the Setup Assistant opens automatically and pre-flights the three things a mount depends on: the bundled JuiceFS engine, macFUSE, and backend reachability. You can reopen it any time from the menu-bar icon → Setup Assistant…
Point the app at your NAS
The Setup Assistant (and later Preferences → Connection) wants two fields:
Redis URL: redis://<nas-ip>:30179/1
S3 Endpoint Override: http://<nas-ip>:30151/zpool
The Redis URL identifies the authenticated RESP proxy; it is not a login by itself. The S3 endpoint override is needed when the volume was formatted with a docker-internal hostname, which the shipped compose does, so set it. Then click Save.
Request access
Use Request access in Settings → Server access. An admin approves this Mac on the Manager’s Principals page (http://<nas-ip>:30190/principals.html). The Manager delivers this Mac’s Redis principal and object key after approval; the app stores them in Keychain. Wait for approval before starting the mount.
step 5 of 8
First mount
Enable the bundled eject helper if macOS prompts, then hit Start in the popover. /Volumes/zpool appears in Finder.
The eject helper and the admin prompt
The bundled eject helper must be enabled and answering in System Settings → General → Login Items & Extensions before the app starts a mount. Start at login is a separate app preference; it is not the eject-helper approval. mount_nfs itself runs without elevation. macOS may ask for administrator approval only when JuiceMount creates the mount folder.
What success looks like
The volume shows up under Locations in Finder at /Volumes/zpool (the mount point derives from the volume name in Preferences → Connection). Point your NLE’s media browser at it and edit. Use the same mount name on another Mac if your project relies on absolute paths. The final two-Mac workflow, including simultaneous project editing, is still under test.
The menu-bar states
The menu-bar icon is the status system; the tint is the signal. These are the marks the app ships:
-
Green: healthyAll systems healthy: Redis, MinIO, FUSE, and the NFS mount all reachable. At 50% opacity it means idle: the server isn’t started.
-
Amber: degradedRunning, but a backend (Redis / MinIO / FUSE / NFS) is unhealthy or recovering. The popover names which one and why.
-
Blue: offline-files modePinned files served locally at SSD speed; un-pinned reads fail fast instead of hanging.
-
Red: faultUnreachable, start failed, or disconnected. The failure message surfaces in the popover.
A small blue up-arrow badge appears bottom-right of the mark while spool uploads are draining.
If the volume doesn’t appear: open the popover. If the NFS row says “Volume not mounted”, click Mount Now to retry the ordinary NFS mount; administrator approval may be needed only when JuiceMount creates the mount folder. Also check that something else doesn’t already own the path:
mount | grep <volume-name>
step 6 of 8
Make it yours
Mounted is the baseline. These are the habits that make it an editing drive: pin what you’re cutting, flip offline mode when you leave, and decide whether writes should spool.
Pin folders for offline
Popover → Pin Folder for Offline…, or right-click a folder in Finder → Services → JuiceMount: Pin for Offline. A prefetcher pulls every byte to local SSD and shows per-folder progress in the popover.
macOS hides Services items by default. Enable once in System Settings → Keyboard → Keyboard Shortcuts → Services → Files and Folders, check “JuiceMount: Pin for Offline”. May need a killall Finder to refresh.
Flip offline mode when you leave
One toggle in the popover (cellular, plane, NAS down): pinned files keep reading at SSD speed; un-pinned reads refuse in 4–67 ms instead of hanging Finder on a ~30 s NFS retry. Back online, Sync Now runs verify-and-repair on the pin set, re-fetching anything the cache evicted.
Decide whether writes spool
Preferences → Cache & Storage. On (the default): acknowledged writes stay on local storage while the background drainer publishes them to the server; the popover shows pending uploads until they drain. Off: writes depend on the server at the time of the save, like any network drive. Note offline-files mode gates reads; it doesn’t make un-spooled writes safe.
Search the whole library
⌘⇧F from any app opens search across the indexed volume: results in tens of milliseconds at 100 K+ entries. Spacebar for Quick Look, Enter to reveal in Finder, or drag results straight into a Premiere, Resolve, or FCPX timeline. Toggleable in Preferences if another app owns the hotkey.
Set the cache, reclaim space
Set the cache size in Preferences → Cache & Storage. JuiceMount grows the cache only as far as needed to keep pinned content fully cached and never squeezes the boot disk below a hard 10 GiB free floor. The popover’s disk row has a Reclaim button that thins Time Machine local snapshots (APFS purgeable space), so “disk full” usually isn’t.
Move your library in, keep the stack current
Already have terabytes on the NAS? Open the Manager at http://<nas-ip>:30190 → Migrations tab: browse a read-only source, pick a destination, watch live progress. Junk files (.DS_Store, ._*, Thumbs.db, .sync.ffs_db) are excluded and permissions are not preserved by default, so files land readable on the Mac; jobs queue and run sequentially. Deletes on new installs go to JuiceFS trash for 7 days, browsable in the Manager’s Trash tab.
To update the stack later, follow the upgrade sections of the changelog for the release you are moving to. Render the new compose with the same durable credentials and the new manifest’s image digests, and keep the working stack and its data in place until the rendered file checks out. An existing pre-proxy installation moves with server/scripts/rollout-compose.sh --cutover-proxy, not by re-applying the YAML.
Data lives in the host paths from your volumes: lines, so container churn never touches it. And remember what this isn’t: a backup. It’s primary storage with a cache; run real backups of the MinIO bucket and Redis.
step 7 of 8
Working remotely
The steps above did the storage side: the volume, the cache, pins, the spool. What JuiceMount deliberately doesn’t do is routing: when you leave the LAN, your Mac needs a trusted private route back to the NAS. A Tailscale subnet router or an existing WireGuard, QNAP, or UniFi route can supply this path. Keep Redis, MinIO, and Manager off the public Internet.
Three steps to a private route
Tailscale is a third-party mesh VPN, free for personal use, not affiliated with this project. A Tailscale subnet router is one way to give the Mac a route to the NAS’s LAN; WireGuard or your router’s VPN work the same way.
- Establish the route. Set up a trusted system private route from the Mac to the NAS. The 0.5 client uses the Mac’s normal route to its configured Redis and MinIO endpoints.
- Verify both endpoints from the remote network before testing JuiceMount: the Mac must reach 30179 and 30151 on the NAS, the same test as step 3.
- Select Use existing private network in JuiceMount. Keep the connection fields from step 4 pointed at an address the route reaches.
The final Link routing and reconnection workflow remains under test. Never publish the proxy, MinIO or the Manager to the Internet to make remote access work.
What remote actually feels like
- Pinned files play at SSD speedPin the project before you leave (step 6): every byte is already on local SSD, so playback doesn’t touch the link at all.
- First-touch reads stream at link speedUn-pinned files come over the tunnel as you touch them: scrub a 100 GB file and only the blocks you touch cross the wire, at whatever the connection gives, hotel Wi-Fi included.
- Writes land locally and upload behind youWith the write spool on (the default, step 6), a write acks the moment it’s durable on local SSD, then trickle-uploads at whatever the network allows; the popover shows pending uploads until they drain.
- Offline mode refuses fastIf the link is gone entirely, flip offline-files mode: pinned files keep reading, and un-pinned reads fail in 4–67 ms instead of hanging Finder on a ~30 s NFS retry.
Success for this step: the volume mounts over your private route, from a network that isn’t your LAN.
step 8 of 8
If something breaks
Open the popover first. The health rows (Redis, MinIO, FUSE, NFS mount) are the first thing to check; most fixes start there.
Symptoms and fixes
The volume doesn’t appear in Finder
If the popover’s NFS row says “Volume not mounted”, click Mount Now to retry the ordinary NFS mount; administrator approval may be needed only when JuiceMount creates the mount folder. Check that the bundled eject helper is enabled in System Settings → General → Login Items & Extensions. Also check that something else doesn’t already own the path with mount | grep <volume-name>.
Finder says “not responding”, or the icon turns amber
Amber means degraded: running, but a backend is unhealthy or recovering, and the popover names which one and why. Give it a moment: the health monitor force-remounts a wedged FUSE daemon once the backend is reachable again (about 15 s after the network returned, in the controlled long-outage repro). If the kernel mount itself is wedged (server died, every Finder access hangs), Force Eject in the popover is the last resort: a privileged kernel-level unmount behind a confirmation dialog, after which in-flight operations fail with I/O errors rather than hanging.
Uploads look stuck
With the spool enabled, the popover’s Pending uploads section shows pending, in-flight, stalled, and failed counts with per-entry age and last error; Retry failed and Recover stalled act on them directly. A full spool surfaces to Finder as “disk full” rather than a mystery error.
Search returns no results
Click Sync Now in the popover; the search index rebuilds at the end of every sync. Verify the metadata cache has entries: the popover should show an entry count above zero.
The menu-bar icon doesn’t appear
The app has no Dock icon by design; look at the right side of the menu bar. If Activity Monitor shows a JuiceMount process but no icon, refresh the menu bar with killall SystemUIServer.
“Damaged or unsigned” warning on launch
You shouldn’t see this with the signed release from GitHub. It turns up mainly on an unsigned copy passed around by AirDrop or a third party; get the release from GitHub instead, because only a JuiceMount Developer ID signed app can start a mount. Fix with xattr -d com.apple.quarantine /Applications/JuiceMount.app, or approve under System Settings → Privacy & Security → Open Anyway.
Server-side: init exit codes, empty migrations, unreadable files
juicefs-init exit codes are in step 3: code 2 is MinIO unreachable, 3 is Redis, 4 is bad credentials, 5 is a malformed bucket URL, 6 is the format itself. Two more from the field: a Manager copy that reports “0 files / 0 B” means a stale image, so render the compose with the current release manifest’s image digests and redeploy. And if the Mac can’t open migrated files, the source had restrictive permissions; either un-tick Preserve permissions before migrating, or chmod -R u+rwX,g+rwX,o+rX on the destination. docker compose logs <service> has the full startup output of any container.
You want to re-run first-time setup
Menu-bar icon → Setup Assistant… reopens onboarding any time: it pre-flights the bundled JuiceFS engine, macFUSE, and backend reachability, and re-points the app at your NAS, the same fields as Preferences → Connection.
Logs and diagnostics
Structured JSON logs live at ~/Library/Logs/JuiceMount/juicemount.log (16 MB × 5 rotation); the JuiceFS daemon’s own log is auto-tailed into it with warnings promoted. For live debugging:
tail -f ~/Library/Logs/JuiceMount/juicemount.log | jq .
For a bug report, use Export Diagnostics… (in the popover and in Preferences → Maintenance): it bundles logs, the mount table, and backend health into a local zip; nothing is sent anywhere.
when you leave
Uninstalling
One script removes JuiceMount’s per-user state from the Mac. It is careful by design: it stops the app, unmounts, shows exactly what it will remove with sizes, and asks once before touching anything.
./scripts/uninstall.sh # interactive
./scripts/uninstall.sh --dry-run # show the plan, change nothing
./scripts/uninstall.sh --yes # skip the main confirmation
./scripts/uninstall.sh --yes --delete-pending-uploads # fully unattended
The spool check matters. If the write spool still holds files, those are writes that were acknowledged to Finder but never finished uploading to your NAS, so deleting them is permanent data loss. The script requires its own explicit confirmation for that step (or the --delete-pending-uploads flag). To drain them instead: relaunch JuiceMount, enable the spool, and wait for pending uploads to reach zero.
- The LaunchAgent (
~/Library/LaunchAgents/com.juicemount.agent.plist) - A legacy sudoers rule from older versions (
/etc/sudoers.d/juicemount-mount, the one step that asks for sudo) - App state at
~/.juicemount: the local metadata cache (rebuilt from Redis on a future reinstall) and the pin list, which lives here too and is not recovered - App support at
~/Library/Application Support/JuiceMount, including the spool - Logs at
~/Library/Logs/JuiceMount - The JuiceFS chunk cache at
~/.juicefs/cache, usually the big one; it can be hundreds of GB, and the size is shown before you confirm - The app’s UserDefaults
/Applications/JuiceMount.app: drag to Trash yourself- A Homebrew
juicefsbinary from an older install, if you have one:brew uninstall juicefs - macFUSE: remove via System Settings or its installer
- Everything on your NAS: Redis and MinIO are untouched; your media stays where it is
Your bytes were never locked in to begin with: file data sits in JuiceFS’s open, documented chunk format in your bucket, and the stock juicefs client can mount the volume with no JuiceMount involved.