Skip to content

JuiceFS

Mount an S3-backed POSIX filesystem as a managed addon (host binary).

JuiceFS puts a real POSIX filesystem on top of object storage: file data is split into chunks and stored in an S3 bucket, while a separate metadata engine (SQLite, Redis, PostgreSQL…) keeps the namespace, file attributes and chunk maps. You get an ordinary directory you can ls, cat and grep — every write eventually lands as immutable objects in the bucket. It solves the “I want a filesystem, but the bytes should live in S3” problem: snapshots are cheap (objects are immutable), capacity is the bucket’s, and the same bucket can be re-mounted from any host that knows the metadata URL.

As a pgcli addon, JuiceFS is pgcli’s first host-binary addon: there is no container. pg addon install juicefs downloads the official binary from GitHub releases (sha256-verified; a juicefs already on PATH is reused), formats the volume against an external S3 service and mounts it on the host.

Platform: Linux only (macOS refuses with a clear error, like BeeGFS). The mount is a plain host directory — visible to host processes only. It does NOT propagate into running PG instance containers (no rshared mount propagation in v1; the addon manages this host’s mount, not a cluster’s).

Everything is explicit

Both sides of the volume are named on the command line — pgcli never auto-detects or inherits from same-host addons:

  • Object store: an EXTERNAL S3 service (cloud, or a MinIO on another machine) given as --s3-endpoint http(s)://host[:port] plus --access-key/--secret-key. pgcli probes the endpoint before formatting, so an unreachable host fails fast with “not answering” instead of hanging inside juicefs format. The endpoint is baked into the volume metadata at format time — later mounts need only the metadata URL.
  • Metadata engine: an explicit URL — --dsn (alias --meta-dsn) accepting postgres://, redis:// or sqlite3://. pgcli never creates or drops the database; for PostgreSQL it must already exist.
  • Bucket: never created by pgcli. Provision it yourself (once) with the pg mc wrapper — the install prints the exact two lines:
pg mc alias set externals3 http://minio.example.com:9000 AK '***'
pg mc mb externals3/juicefs-data
pg addon install juicefs --name data \
    --s3-endpoint http://minio.example.com:9000 \
    --access-key AK --secret-key SK \
    --bucket juicefs-data \
    --dsn "sqlite3:///var/lib/juicefs/data.db"

Any engine works the same way — pick the URL:

# PostgreSQL: the database must already exist (pgcli never creates or drops
# it); juicefs builds its own jfs_* tables inside.
--dsn "postgres://jfs:pass@meta-host:5432/jfs?sslmode=disable"

# Redis (+TLS): the /N suffix selects the logical database.
--dsn "redis://:pass@redis-host:6379/0"
--dsn "rediss://user:pass@redis-host:6380/1"

The mount point is yours to choose — by default it lives under pgcli’s addon dir; point it anywhere with --mount-point. Create the directory yourself (mkdir -p) at a path the user running pg has read-write access to:

mkdir -p /mnt/cloudfs
pg addon install juicefs --name data \
    --s3-endpoint https://s3.amazonaws.com \
    --access-key AK --secret-key SK \
    --dsn "postgres://jfs:pass@meta-host:5432/jfs?sslmode=disable" \
    --bucket juicefs-data --mount-point /mnt/cloudfs

The three engines can all be pgcli’s own services — created with pgcli, then wired in explicitly as ordinary external URLs (the install never looks them up by itself; it could equally well point at a managed DB or a cloud bucket):

# PostgreSQL metadata: create the instance, then the database (pgcli's
# install path never creates or drops it). `pg status` prints the admin
# connection URL — swap its /postgres path for the new database.
pg create meta-pg
pg exec meta-pg -c "CREATE DATABASE jfs"
pg status meta-pg

pg addon install juicefs --name data \
    --s3-endpoint http://minio-host:9000 --access-key AK --secret-key SK \
    --bucket juicefs-data \
    --dsn "postgres://admin:PASS@127.0.0.1:35432/jfs?sslmode=disable"
# Redis metadata (a pgcli redis addon; `pg addon password redis` returns the
# generated password bare — paste it in, and /N picks the logical database):
pg addon install redis --name mred
pg addon password redis --name mred
pg addon install juicefs --name r1 \
    --s3-endpoint http://minio-host:9000 --access-key AK --secret-key SK \
    --bucket juicefs-r1 --dsn "redis://:PASS@127.0.0.1:6379/1"
# MinIO as the object store (a pgcli minio addon — or silo/rustfs/garage/
# seaweedfs; create the bucket by hand, pgcli never does; the root user is
# admin unless --root-user said otherwise, and `pg addon password minio`
# returns the secret key bare):
pg addon install minio --name ms3
pg mc mb ms3/juicefs-data
pg addon install juicefs --name m1 \
    --s3-endpoint http://127.0.0.1:9000 --bucket juicefs-data \
    --access-key admin --secret-key "$(pg addon password minio --name ms3)" \
    --dsn "sqlite3:///var/lib/juicefs/m1.db"

These examples run pg on the same machine as the store, so loopback (default 127.0.0.1) is enough. When the JuiceFS client lives on another host, publish the store on the network and point --s3-endpoint at its address — the endpoint is baked into the volume at format time, so publish it from the start:

pg addon install minio --name ms3 --listen 0.0.0.0   # --force if already installed
# from the other host:
pg addon install juicefs --name m1 --s3-endpoint http://10.0.0.5:9000 …

(The same note applies to the redis example: redis already binds 0.0.0.0 by default — its password is the gate.)

The same volume can then be mounted from any host that has the metadata URL — the credentials and endpoint already live inside it.

Parameters

Flag Default Description
--name juicefs Volume name (config map key; also the JuiceFS volume name at format time)
--dsn / --meta-dsn — REQUIRED. Metadata-engine URL: postgres://, redis:// or sqlite3://
--s3-endpoint — REQUIRED. External S3 service as http(s)://host[:port] (no bucket path, no embedded credentials)
--access-key / --secret-key — REQUIRED together. S3 credentials for the external service
--bucket juicefs-<name> Bucket name — pgcli does NOT create it (see above)
--mount-point <base>/addon/juicefs/<name>/mnt Host directory to mount — pgcli never creates it: mkdir -p it beforehand, writable for the user running pg
--cache-dir <base>/addon/juicefs/<name>/cache Local data cache dir
--install-dir /usr/local/bin Where the host binary goes (falls back to ~/.local/bin when not writable — keep it on PATH)
--version pinned (1.4.1) Release to download (its sha256 is pinned in pgcli; pass a version whose digest pgcli knows)
--no-mount off Format and configure, leave it unmounted
--force off Rebuild the config entry from the flags (format is still skipped when the volume already exists in the metadata engine)

FUSE itself (/dev/fuse + fusermount3) is probed, never installed — a missing fuse3 package fails the install with the apt line to run.

Daily operations

pg addon juicefs status --name data      # endpoint, bucket, meta URL, binary, mount state
pg addon juicefs umount --name data
pg addon juicefs mount --name data       # juicefs mount -d self-daemonizes
pg addon juicefs mount --name data --mount-point /mnt/cloudfs
                       # mount at a custom path (mkdir -p it first); while
                       # unmounted this also PERMANENTLY relocates the volume
pg addon start juicefs --name data       # top-level aliases of mount/umount
pg addon stop  juicefs --name data
pg logs addon juicefs --name data -f     # tail the mount log
pg addon password juicefs --name data    # the stored S3 secret key
pg addon remove juicefs --name data      # unmount + forget; bucket, metadata, binary stay
pg addon remove juicefs --name data --clean-data   # also deletes the addon host dir

remove never deletes S3 data or the metadata store — losing the --dsn target means losing the filesystem. The mount owner is the user running pg.

Moving to a different mount point

The mount point is recorded on the config entry, and mount accepts a --mount-point override that relocates the volume — the new path is persisted to pg.yaml, so later plain mounts (and autostart) use it. The old mount must be down first, and the new directory must exist and be writable (same contract as install):

pg addon juicefs umount --name data
mkdir -p /srv/cloudfs
pg addon juicefs mount --name data --mount-point /srv/cloudfs
#   [i] mount point moved to /srv/cloudfs (saved)
# ✓ juicefs "data" mounted at /srv/cloudfs

Attempting the move while still mounted refuses with an “umount first” hint; pointing it at a missing directory fails with the mkdir -p remedy. The mount point is fully decoupled from pgcli’s bookkeeping (pid/log/cache stay under <base>/addon/juicefs/<name>/), so relocation never disturbs the volume itself — it is the same bucket and metadata re-attached elsewhere.

Autostart

pg autostart enable --juicefs --name data

JuiceFS is a host-binary addon, so pg start --autostart re-mounts the volume at boot instead of starting a container. The backend is external — boot succeeds only when the S3 endpoint is reachable again. It never re-formats. The mount comes up as the user the boot service runs as.