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 juicefs binary.
  • 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.

ServiceWhat it doesPort
redisJuiceFS metadata authority (AOF persistence), internal only—
respproxyAuthenticated Redis gateway for named principals30179
minioS3-compatible object store for JuiceFS chunks30151 (S3), 30152 (console)
minio-keysProvisions separate client and GC object keys—
juicefs-initOne-shot pre-flight checks + first-time volume format—
juicefsLive FUSE mount + loopback-only WebDAV127.0.0.1:30180
juicemount-managerControl-plane web UI (migrations, trash, backups, maintenance, settings) and Mac enrollment30190
juicefarmNAS CPU derivative worker—

Watch it come up

stylized: the real check is docker compose ps
servicestate
redis healthy
respproxy healthy, RESP on :30179
minio healthy
minio-keys exited (0), object keys provisioned
juicefs-init exited (0), volume formatted
juicefs healthy, mounted
juicemount-manager healthy, web UI on :30190

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

30179 RESP proxy: authenticated Redis metadata required
30151 MinIO S3: file data required
30152 MinIO console: web UI diagnostic
30190 JuiceMount Manager: Mac enrollment, migrations, trash, backups, settings required
The mount needs 30179 and 30151, and the Mac enrolls through the Manager on 30190. The console is how you look the stack in the eye. WebDAV (30180) is bound to 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>:30152 should show a login page.
  • Manager web UI: http://<nas-ip>:30190 should 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.

CodeMeaning
2MinIO unreachable from inside the container network
3Redis PING failed
4Credentials empty, whitespace, or placeholder pattern: fix the environment file and render again
5JM_BUCKET_URL missing the http:// scheme
6juicefs 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

terminal · on the Mac
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.

terminal · on the Mac
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…

stylized: the real window walks you through fixing whichever row fails

Point the app at your NAS

The Setup Assistant (and later Preferences → Connection) wants two fields:

preferences → connection
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:

terminal
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 for offline

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.

offline-files mode

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.

write spool · on by default

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.

instant search

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.

cache and disk

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.

migrate + maintain

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.

Your Mac reaches the NAS from any network over a private route. The route carries the required ports from step 3: 30179 (the RESP proxy), 30151 (MinIO) and 30190 (the Manager). JuiceMount does the storage and configuration; the route does the routing.

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.

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

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

terminal, from the cloned repo
./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.

what it removes
  • 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
what it deliberately leaves
  • /Applications/JuiceMount.app: drag to Trash yourself
  • A Homebrew juicefs binary 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.