# Telnyx Storage: CloudFS (Beta) — Full Documentation > Complete page content for CloudFS (Beta) (Storage section) of the Telnyx developer docs (https://developers.telnyx.com). > Root index: https://developers.telnyx.com/llms.txt · Lightweight index for this subsection: https://telnyx.com/llms/storage/cloudfs-beta.txt ## Get Started ### CloudFS > Source: https://developers.telnyx.com/docs/edge-compute/cloudfs.md CloudFS is a POSIX filesystem you mount on any host or container. Once mounted, it behaves like a local directory — `read`, `write`, `mkdir`, `rename`, `git init` — but every byte is stored durably in Telnyx Cloud Storage, and the same filesystem can be [mounted by many clients at once](/docs/edge-compute/cloudfs/concurrent-access) — a fleet of agents reading and writing it concurrently. You mount it with the open-source [JuiceFS Community Edition](https://github.com/juicedata/juicefs) client (Apache-2.0), which you run yourself — Telnyx does not host or bundle it. JuiceFS has **no server process**: the client does all the filesystem work and talks directly to a metadata database and to object storage, so **no JuiceFS component runs on the Telnyx side**. Telnyx provides and authenticates the two managed backends — the metadata database and object storage — and hands you a ready-to-mount filesystem. The target use case is a **shared, persistent filesystem for AI agents**: provision one CloudFS, mount it once, and every file, repo, and checkpoint an agent produces is there — and still there on the next mount, from anywhere. **Do not touch the `cloudfs-fs-*` bucket directly.** A CloudFS filesystem's data lives in a bucket named `cloudfs-fs-` inside **your own Telnyx Cloud Storage account** — it appears alongside your other buckets and is reachable with your API key. Its objects are **opaque JuiceFS blocks** (`chunks/…` plus internal bookkeeping), not your files. Deleting, renaming, or editing objects in that bucket out of band **corrupts the filesystem** — a missing block is unrecoverable and there is no repair path. Manage a CloudFS filesystem only through the [CloudFS API](/api-reference/cloudfs-filesystems/list-cloudfs-filesystems) and a [JuiceFS mount](/docs/edge-compute/cloudfs/mount); treat its bucket as internal, hands-off storage. CloudFS is in **beta**. The API surface and behavior may still change as it moves toward general availability. ## Next Steps - [Quick Start](/docs/edge-compute/cloudfs/quickstart) — Create a filesystem and mount it end-to-end - [How CloudFS Works](/docs/edge-compute/cloudfs/concepts/how-cloudfs-works) — The two-lane architecture and credentials - [Mounting](/docs/edge-compute/cloudfs/mount) — The verified `juicefs mount` recipe - [Concurrent Access](/docs/edge-compute/cloudfs/concurrent-access) — Mount the same filesystem from many clients at once - [API Reference](/api-reference/cloudfs-filesystems/list-cloudfs-filesystems) — The `/v2/storage/cloudfs` endpoints ## Related Resources - [Cloud Storage authentication](/docs/cloud-storage/authentication) — How the API key is used as the S3 access key - [Object Storage](/docs/cloud-storage/overview) — The S3-compatible storage CloudFS is backed by --- ### Quick Start > Source: https://developers.telnyx.com/docs/edge-compute/cloudfs/quickstart.md Get up and running with CloudFS: create a filesystem over the API, then mount it with the JuiceFS client inside a Linux container and use it like any local directory — write files, read them back, run `git`. The whole path below is verified end-to-end against production. CloudFS is built on [JuiceFS Community Edition](https://github.com/juicedata/juicefs) (see [How CloudFS Works](/docs/edge-compute/cloudfs/concepts/how-cloudfs-works) for the architecture). There is no server process to run: the JuiceFS client on your host talks directly to a per-filesystem metadata database and to Telnyx Cloud Storage. That means you hold two credentials — the `meta_token` (returned on create) and your `TELNYX_API_KEY` — and the client does the rest. ## 1. Create a Filesystem `POST /v2/storage/cloudfs`. The `Idempotency-Key` header is **required** (a request without it is rejected with `400`), and so is `region` — there is no default. Allowed regions are `us-central-1`, `us-east-1`, and `us-west-1`. ```bash curl -X POST https://api.telnyx.com/v2/storage/cloudfs \ -H "Authorization: Bearer $TELNYX_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{"name": "agent-fs", "region": "us-east-1"}' ``` Replaying the same `Idempotency-Key` returns the same filesystem instead of creating a second one. A `201` response looks like this: ```json { "data": { "record_type": "cloudfs", "id": "0195e0a2-3f4b-4c8d-9a1e-7b62d4c90f13", "name": "agent-fs", "status": "ready", "meta_url": "postgres://fs_9f3c8e2a4b7d01c6:cloudfs_tok_...@us-east-1.telnyxcloudfs.com:5432/fs_9f3c8e2a4b7d01c6?sslmode=require", "meta_token": "cloudfs_tok_...", "s3_endpoint": "https://us-east-1.telnyxcloudstorage.com", "s3_bucket": "cloudfs-fs-9f3c8e2a4b7d01c6", "region": "us-east-1", "created_at": "2026-07-14T21:42:01.384912Z", "updated_at": "2026-07-14T21:42:03.192847Z" } } ``` **This is the only time you see the token.** `meta_url` (with the token inline as the password) and the standalone `meta_token` are returned **only** on create and on [rotate](/api-reference/cloudfs-filesystems/rotate-the-metadata-token). `GET /v2/storage/cloudfs/{id}` returns `meta_url` **without** the token and no `meta_token` at all — it cannot be read back. Store the token securely now; if you lose it, rotate to get a new one. You'll use two values from this response to mount: - `meta_url` — the full connection string, token included. This is the metadata endpoint; the host is always `us-east-1.telnyxcloudfs.com` regardless of the filesystem's region. - `id` — the filesystem UUID, for later API calls (detail, rename, rotate). The response's `s3_bucket` (`cloudfs-fs-`) is in **your** Telnyx Cloud Storage account, but it holds CloudFS's internal blocks — not your files. Never modify or delete its objects directly; that corrupts the filesystem. Read and write only through the mount below. See [How CloudFS Works](/docs/edge-compute/cloudfs/concepts/how-cloudfs-works#the-data-lane). ## 2. Mount It with JuiceFS CloudFS **pre-formats** the volume during provisioning — the bucket already contains the JuiceFS volume metadata. Do **not** run `juicefs format`. The filesystem is already formatted; running `format` against it fails with `cannot update volume name`. Go straight to `juicefs mount`. (The one exception: if the filesystem's status is `needs_format`, it *does* need a one-time `juicefs format` before mounting — see [Mounting](/docs/edge-compute/cloudfs/mount#formatting-a-needs_format-filesystem).) Mount inside a Linux container with FUSE — a portable path that runs the same anywhere and needs no FUSE install on the host. (You can also mount natively on any Linux or macOS host that has FUSE/macFUSE installed.) The data lane authenticates to Telnyx Cloud Storage over S3: your `TELNYX_API_KEY` is the **access key**, and the secret key can be **any non-empty placeholder** (Telnyx Storage ignores the SigV4 signature, but JuiceFS's AWS SDK rejects an empty secret). See [Cloud Storage authentication](/docs/cloud-storage/authentication) for why the secret is ignored. ```bash # Start a Linux container with FUSE access, passing your API key through. docker run --rm -it --privileged --device /dev/fuse \ -e TELNYX_API_KEY \ ubuntu:24.04 bash ``` ```bash # --- inside the container --- # Install the JuiceFS Community Edition client apt-get update && apt-get install -y curl fuse curl -sSL https://d.juicefs.com/install | sh - # Point JuiceFS at your S3 credentials: export AWS_ACCESS_KEY_ID="$TELNYX_API_KEY" # S3 access key = your Telnyx API key export AWS_SECRET_ACCESS_KEY="cloudfs-unused" # any NON-EMPTY value; the signature is ignored # Mount with the meta_url from step 1 (token inline). --background --log keeps # the mount off your terminal; --no-usage-report opts out of JuiceFS telemetry. mkdir -p /mnt/agentfs juicefs mount --no-usage-report --background --log /tmp/juicefs.log \ "postgres://fs_9f3c8e2a4b7d01c6:cloudfs_tok_...@us-east-1.telnyxcloudfs.com:5432/fs_9f3c8e2a4b7d01c6?sslmode=require" \ /mnt/agentfs ``` Check `/tmp/juicefs.log` for progress. Once mounted, `df -h /mnt/agentfs` shows the volume. ## 3. Smoke-Test Over POSIX The mount is a normal directory. Write a file, read it back, and initialize a git repo — all standard POSIX, no CloudFS-specific calls. ```bash cd /mnt/agentfs # Write and read back echo "hello cloudfs" > hello.txt cat hello.txt # -> hello cloudfs # git works against the mounted filesystem git init -q git -c user.email=agent@example.com -c user.name=agent add hello.txt git -c user.email=agent@example.com -c user.name=agent commit -q -m "first commit" git log --oneline # -> the commit you just made ``` Data written here lands in Telnyx Cloud Storage as 4 MiB block objects under `chunks/…` in the filesystem's bucket, and persists across unmount and remount — remount with the same `meta_url` and your files are still there. ## Next Steps - [How CloudFS Works](/docs/edge-compute/cloudfs/concepts/how-cloudfs-works) — the metadata and data lanes, the two credentials, and the on-disk layout - [Mounting](/docs/edge-compute/cloudfs/mount) — the mount recipe in depth, FUSE requirements, and remount - [API Reference](/api-reference/cloudfs-filesystems/list-cloudfs-filesystems) — the full endpoint surface, including rotate and delete - [Overview](/docs/edge-compute/cloudfs) — what CloudFS is and when to use it --- ## Concepts ### Filesystems From First Principles > Source: https://developers.telnyx.com/docs/edge-compute/cloudfs/concepts/filesystems-from-first-principles.md CloudFS gives you a POSIX filesystem you mount on any host or container, backed by Telnyx Cloud Storage. To understand how it works — and why it is shaped the way it is — these Concepts pages build the idea from the bottom up: 1. **What a filesystem is and how a local one is built** (this page) 2. [Why past network filesystems fall short, and how the early cluster filesystems reframed storage](/docs/edge-compute/cloudfs/concepts/network-filesystems) (NFS and AFS → GFS, HDFS, MooseFS) 3. [How CloudFS puts it together](/docs/edge-compute/cloudfs/concepts/how-cloudfs-works) The framing follows the standard treatment in [*Operating Systems: Three Easy Pieces*](https://pages.cs.wisc.edu/~remzi/OSTEP/) (OSTEP), by Remzi and Andrea Arpaci-Dusseau. It is worth reading in full; these pages borrow just enough to motivate the design. ## What a Filesystem Actually Is Strip away the tooling and a filesystem is [two abstractions](https://pages.cs.wisc.edu/~remzi/OSTEP/file-intro.pdf): - A **file** is a linear array of bytes with a **low-level name** — a number, the **inode number**. The operating system does not care whether those bytes are a JPEG or C source; it stores them and hands them back intact. - A **directory** is itself a file, but its contents are specific: a list of `(human-readable name → inode number)` pairs. Nest directories inside directories and you get the tree you navigate every day. So underneath a familiar path like `/home/agent/work.log`, a filesystem is really two things: **an index** that maps names to inodes and inodes to the location of bytes, and **the bytes themselves**. Everything else — `open`/`read`/`write`/`close`, permissions, hard and symbolic links — is built on top of those two. ## How a Local Filesystem Is Built On a single disk, those two things live in two different places. OSTEP's teaching filesystem, [vsfs](https://pages.cs.wisc.edu/~remzi/OSTEP/file-implementation.pdf), divides the disk into an **inode table** plus small **bitmaps** that track which inodes and blocks are free — this is the metadata index — and a much larger **data region** of fixed-size blocks that holds file contents. Each inode is a compact record of metadata (permissions, timestamps, size) plus a **multi-level index** of pointers to the file's data blocks. Reading a file means reading its inode to find the block pointers, then reading the blocks; writing a file may also flip an allocation bit and rewrite the inode. The [Fast File System](https://pages.cs.wisc.edu/~remzi/OSTEP/file-ffs.pdf) added the lesson that *where* you place those blocks matters: the original Unix filesystem treated the disk like RAM and paid for it in seeks, so FFS made the filesystem **"disk-aware"** — keep an inode near its data, keep files of one directory together, respect the physical medium. The durable takeaway is this: **a filesystem is a metadata index over a pile of data blocks — two kinds of state with two very different access patterns.** Metadata is small, hot, and demands consistency: an inode is either allocated or it is not. Data is large and wants throughput. Hold onto that split; it is the key to everything that follows. ## Staying Consistent Across Crashes The metadata index has one more demand: it must survive a crash. A single logical operation — appending a block to a file, say — touches several structures (the allocation bitmap, the inode, the data block), but the disk writes them one at a time. Lose power in between and the index is left inconsistent: a block marked used by no file, or an inode pointing at garbage. Early filesystems repaired this after the fact with **fsck**, scanning the whole disk on reboot — which stops scaling as disks grow. The durable fix, [**journaling** (write-ahead logging)](https://pages.cs.wisc.edu/~remzi/OSTEP/file-journaling.pdf), is borrowed straight from databases: write your intended changes to a **log** and commit them there first, then apply them to their real homes; after a crash, replay the committed log. Recovery then costs the size of the log, not the size of the disk. Keep this one in mind — it is the cleanest argument for *where* CloudFS keeps its metadata. And the medium underneath can lie: a sector rots, or silently returns the wrong bytes. Filesystems and storage layers defend against it with [checksums and scrubbing](https://pages.cs.wisc.edu/~remzi/OSTEP/file-integrity.pdf); CloudFS leaves that to the object store, which does it for you. ## Further Reading - [*Operating Systems: Three Easy Pieces*](https://pages.cs.wisc.edu/~remzi/OSTEP/) — [Files and Directories](https://pages.cs.wisc.edu/~remzi/OSTEP/file-intro.pdf), [File System Implementation](https://pages.cs.wisc.edu/~remzi/OSTEP/file-implementation.pdf), [Locality and the Fast File System](https://pages.cs.wisc.edu/~remzi/OSTEP/file-ffs.pdf), [Crash Consistency: FSCK and Journaling](https://pages.cs.wisc.edu/~remzi/OSTEP/file-journaling.pdf), and [Data Integrity and Protection](https://pages.cs.wisc.edu/~remzi/OSTEP/file-integrity.pdf). **Next:** [Network Filesystems: From NFS to GFS](/docs/edge-compute/cloudfs/concepts/network-filesystems) — what happens when you stretch this across a network. --- ### Network Filesystems: From NFS to GFS > Source: https://developers.telnyx.com/docs/edge-compute/cloudfs/concepts/network-filesystems.md A local filesystem is [a metadata index over a pile of data blocks](/docs/edge-compute/cloudfs/concepts/filesystems-from-first-principles). To share one between machines you must move one or both of those halves across a network — and decades of distributed filesystems show that is where the difficulty lives. ## NFS: Stateless, and Brittle Over the WAN [Sun's Network File System](https://pages.cs.wisc.edu/~remzi/OSTEP/dist-nfs.pdf) makes the filesystem a client/server protocol. Its central design goal is **simple, fast crash recovery**, achieved by making the server **stateless** — it keeps no per-client state, so every request carries everything needed to service it (a `READ` sends the explicit offset; the client, not the server, tracks position). Most operations are **idempotent**, so the failure handler collapses to a single rule: *set a timer, and if no reply arrives, retry.* Elegant — but it has costs that bite hard over a wide-area network: - **Weak cache consistency.** Clients cache and buffer writes for performance, which introduces the update-visibility problem. NFS papers over it with flush-on-close and an **attribute cache with a timeout** (~3 seconds), so whether a client sees the latest version of a file "depends on whether the cache entry has timed out" — the source of NFS's notorious occasional stale reads. - **The hang.** Because the client simply retries until the server answers, an unresponsive or partitioned server makes the client **block indefinitely**. A `read()` that never returns takes the calling process — and often a whole pipeline — down with it. A network partition is indistinguishable from a crash. - **Small files are easy; large files are not.** Over a LAN with small files NFS is fine. Over a WAN, file seeks and mid-file updates on large files get ugly, and rapid writes suffer under lost packets. Put it concretely with a plain client/server mount. The server exports a directory; a client mounts it and it looks local: ```bash # on the client — mount an export from the NFS server mount -t nfs fileserver:/export/shared /mnt/shared ``` With a default hard mount, if `fileserver` crashes or the network partitions, any process that so much as `stat`s a path under `/mnt/shared` blocks in uninterruptible sleep — `ls /mnt/shared` hangs, shrugs off Ctrl-C, and the console fills with `NFS server fileserver not responding, still trying`. The mount doesn't return an error; it waits, and takes the caller down with it. That is tolerable on a quiet LAN with a reliable server; for agents scattered across the internet — where partitions are routine and every mount is one flaky link from a hung process — it is the wrong default. ## AFS: Whole-File Caching, and Stateful Bookkeeping The [Andrew File System](https://pages.cs.wisc.edu/~remzi/OSTEP/dist-afs.pdf) was built for **scale**. On `open()` it fetches the *whole file* to the client's local disk and serves subsequent reads and writes locally; on `close()` it ships the whole file back. To avoid clients constantly polling "has this changed?", AFS added **callbacks** — a promise from the server to notify a client when a cached file changes — giving **close-to-open** consistency. It scales better than NFS, but the design fights our use case: - **Callback state is awkward for clients that come and go.** A client that was offline may have missed an invalidation and must revalidate its whole cache on return; a rebooted server does not know which clients cache what and must have everyone re-validate. Ephemeral agents — containers that appear, work, and vanish — are exactly the clients this bookkeeping handles worst. - **Whole-file caching punishes the workload we care about.** Fetching and rewriting an entire file just to append one line to a shared worklog, or to touch a small region of a large file, is precisely what AFS does poorly. AFS itself notes that its baseline consistency is not enough for concurrent updates to something like a shared code repository — you still need explicit **file-level locking**. The through-line: NFS and AFS both begin with a filesystem that assumes **one server owns the disk**, and then bolt a network onto it. For a fleet of ephemeral agents working on a shared tree, over the public internet, on top of object storage rather than a local disk, that is the wrong starting shape. ## Separating Metadata From Data By the early 2000s a different design had emerged — built for a fleet of cheap, failure-prone machines from the start, rather than making a single server's disk look remote. ### Google File System [Google File System](https://research.google/pubs/pub51) (GFS, 2003) split the problem cleanly in two: - a single **master** holds all the metadata — the namespace and the map from each file to its **chunks** — in memory, and - a fleet of **chunkservers** holds the file data as large chunks (64 MB), replicated for durability. The move that matters: **the master is kept out of the data path.** A client asks the master *where* a file's chunks live, then streams the bytes **directly** to and from the chunkservers. Metadata (small, hot, consistency-critical) and data (large, bulk) are served by different systems on different paths — the same split we saw *inside* the [local filesystem](/docs/edge-compute/cloudfs/concepts/filesystems-from-first-principles), now drawn across a cluster. ### HDFS and MooseFS [HDFS](https://hadoop.apache.org) is the open-source realization of that design: a **NameNode** holds the namespace and the file→block map — "user data never flows through the NameNode" — while **DataNodes** store the blocks (128 MB) and serve reads and writes directly to clients. [MooseFS](https://moosefs.com) applied the same master-plus-chunkservers split but exposed a full **POSIX filesystem you mount**, closing the gap back to the local-filesystem interface. These systems fixed what NFS and AFS could not: they stopped pretending one server owns a disk, and instead let **an authoritative metadata service** coordinate **a scalable pool of data storage**. What they still asked of you was to run that metadata master and that fleet of data servers yourself — which is exactly the operational burden CloudFS removes. ## Further Reading - [Sun's Network File System (NFS)](https://pages.cs.wisc.edu/~remzi/OSTEP/dist-nfs.pdf) and [The Andrew File System (AFS)](https://pages.cs.wisc.edu/~remzi/OSTEP/dist-afs.pdf), from [OSTEP](https://pages.cs.wisc.edu/~remzi/OSTEP/). - [The Google File System](https://research.google/pubs/pub51) (Ghemawat, Gobioff, Leung), [Apache Hadoop / HDFS](https://hadoop.apache.org), and [MooseFS](https://moosefs.com). **Next:** [How CloudFS Works](/docs/edge-compute/cloudfs/concepts/how-cloudfs-works) — JuiceFS takes this split to its cloud conclusion. --- ### How CloudFS Works > Source: https://developers.telnyx.com/docs/edge-compute/cloudfs/concepts/how-cloudfs-works.md This page is the destination of a short arc. If you want the reasoning behind the design, read the background first: 1. [Filesystems From First Principles](/docs/edge-compute/cloudfs/concepts/filesystems-from-first-principles) — a filesystem is a metadata index over a pile of data blocks. 2. [Network Filesystems: From NFS to GFS](/docs/edge-compute/cloudfs/concepts/network-filesystems) — why bolting a network onto a disk-owning server is brittle, and how GFS, HDFS, and MooseFS split an authoritative metadata service from a scalable pool of data storage. CloudFS is where that arc lands: a POSIX filesystem you mount on any host or container, backed by Telnyx Cloud Storage. ## How CloudFS Does It CloudFS is built on [JuiceFS](https://github.com/juicedata/juicefs), which — in its authors' words — was *"inspired by Google File System, HDFS and MooseFS."* JuiceFS takes the metadata/data split those systems proved at cluster scale and replaces each half with a managed cloud primitive, so you operate neither a metadata master nor a fleet of data servers: - The **metadata index** — the master's job in GFS, the NameNode's in HDFS — becomes a **transactional database**. The database provides strong consistency for the small, hot, correctness-critical half: an entry is committed or it is not, and every client reads the same tree. This is the direct answer to NFS's stale-attribute cache and AFS's callback bookkeeping — put the index in one consistent place that all clients share, rather than caching copies and trying to keep them in sync. It also inherits the database's transactions: [the crash-consistency machinery a local filesystem hand-builds as journaling](/docs/edge-compute/cloudfs/concepts/filesystems-from-first-principles#staying-consistent-across-crashes) — write-ahead log, commit, replay — is simply what the database already does, so a metadata change is one atomic transaction with no partial-update window and no `fsck`-style recovery scan. - The **data blocks** — the chunkservers' job — become **object storage** (Telnyx Cloud Storage). JuiceFS splits every file into a **chunk → slice → block** hierarchy and writes the blocks as ordinary objects; object storage supplies the durability and throughput for the large half. The blocks are opaque — you cannot reassemble a file from the bucket alone, because the index that orders them lives in the metadata database. Object storage also rewards writing new immutable objects over rewriting in place, so JuiceFS follows a [log-structured](https://pages.cs.wisc.edu/~remzi/OSTEP/file-lfs.pdf) discipline: each write becomes a new **slice**, never an overwrite, and a background **compaction** later merges overlapping slices and reclaims the garbage — exactly the cleaner a log-structured filesystem runs. - There is **no CloudFS server in the data path.** JuiceFS is a **"rich client"**: like AFS's client and MooseFS's FUSE mount, all the filesystem logic — the directory tree, inode allocation, splitting files into blocks, caching — runs in the process that mounts the volume. But instead of caching whole files and chasing callbacks, it reads and writes the shared metadata database directly, so consistency comes from the database rather than from a promise a server has to remember. When you `juicefs mount`, the client opens one connection to the metadata database and one to object storage, and serves POSIX calls from those two. CloudFS is Telnyx's managed packaging of [JuiceFS Community Edition](https://github.com/juicedata/juicefs) (Apache-2.0): Telnyx runs and authenticates both the metadata database and the object storage and hands you a formatted, ready-to-mount filesystem. Because JuiceFS Community Edition is a client library with **no server process**, there is no JuiceFS component running on the Telnyx side at all. The server side is a generic managed metadata database and object storage; **every line of filesystem logic runs in the open-source JuiceFS client you mount** — which you can read, pin a version of, and run yourself. CloudFS is that community client plus managed backends and a control-plane API, not a proprietary server tier. For the storage format in depth, see JuiceFS's own [architecture](https://juicefs.com/docs/community/architecture) documentation, including its [how JuiceFS stores files](https://juicefs.com/docs/community/architecture#how-juicefs-store-files) section. ## The Two Lanes Concretely, a CloudFS mount talks to two backends over two independent lanes, each with its own credential. There is no CloudFS server in the request path — your client connects to both directly. | | Metadata lane | Data lane | |---|---|---| | **What it stores** | Directory tree, filenames, inodes, chunk/slice index | File contents, split into 4 MiB block objects | | **Backend** | Per-filesystem metadata database, behind a managed endpoint | Per-filesystem bucket on **Telnyx Cloud Storage** | | **Endpoint** | `us-east-1.telnyxcloudfs.com:5432` (TLS, `sslmode=require`) | `https://.telnyxcloudstorage.com` | | **Location** | Always **us-east-1**, regardless of the filesystem's data region | The **data region** you chose at create time | | **Credential** | `meta_token` (the metadata connection password) | Your `TELNYX_API_KEY` as the S3 access-key | ## The Metadata Lane The client reaches metadata over the connection string CloudFS gives you as `meta_url`: ``` postgres://fs_:cloudfs_tok_...@us-east-1.telnyxcloudfs.com:5432/fs_?sslmode=require ``` A few things are load-bearing here: - **You connect to a managed metadata endpoint, not to the database directly.** The public host `us-east-1.telnyxcloudfs.com:5432` is a pooled front end to a **per-filesystem metadata database** (`fs_`). The database server itself is not directly reachable. - **TLS is required.** The connection uses `sslmode=require`; there is no plaintext metadata path. - **The `meta_token` is the password.** In the `meta_url` above, the `cloudfs_tok_...` embedded before the `@` is the `meta_token` used as the connection password. It is a managed, rotatable credential (see [The Metadata Token Lifecycle](#the-metadata-token-lifecycle)) — not the underlying database's own credential, which you never handle. Metadata is **centralized in us-east-1 for every filesystem**, no matter which data region you pick. A `us-central-1` or `us-west-1` filesystem still has its metadata host set to `us-east-1.telnyxcloudfs.com`. Always use the host embedded in `meta_url`. Because metadata lives only in us-east-1, a client mounting from another region pays a network round-trip on every metadata operation, so metadata-heavy work (listing large trees, many small files) is slower the farther you mount from us-east-1. Data throughput is unaffected — that lane goes to your region's storage endpoint. ## The Data Lane File contents go to a **per-filesystem bucket** named `cloudfs-fs-` on Telnyx Cloud Storage, reached through the S3 endpoint for the filesystem's region (`s3_endpoint`, e.g. `https://us-east-1.telnyxcloudstorage.com`). JuiceFS splits every file into **4 MiB block objects** and writes them under `chunks/…` in that bucket; a 10 MiB file plus a small git repo, for example, lands as dozens of block objects. The blocks are opaque to S3 — reconstructing a file requires the chunk/slice index from the metadata lane. That bucket lives in **your own Telnyx Cloud Storage account** — it shows up in your bucket list and is reachable with your `TELNYX_API_KEY`, exactly like any other bucket you own. But its contents are CloudFS's internal state, not your files: the `chunks/…` objects are opaque 4 MiB JuiceFS blocks, and a small `juicefs_uuid` object holds the volume format. **Do not delete, rename, move, or edit objects in a `cloudfs-fs-*` bucket by hand.** An out-of-band change corrupts the filesystem — a missing or altered block cannot be reconstructed, and there is no `fsck` to repair it. Read and write your files through the mount, and leave the bucket to JuiceFS. Authentication to the data lane reuses your account credentials, following the Telnyx Cloud Storage model: - The **S3 access-key is your `TELNYX_API_KEY`**. - The **S3 secret-key is ignored** — Telnyx Storage does not verify the SigV4 signature (see [Cloud Storage authentication](/docs/cloud-storage/authentication)). You must still supply *some* non-empty secret, because JuiceFS's AWS SDK rejects empty static credentials. CloudFS **pre-formats** the bucket at create time: provisioning writes a `juicefs_uuid` object and formats the volume (JuiceFS volume name `cloudfs-fs-`). Do **not** run `juicefs format` against a `ready` filesystem — it is already formatted, and re-formatting fails with `cannot update volume name`. Just mount. The one exception is a filesystem whose status is `needs_format`, which was provisioned without the final format step: format it once as described in [Mounting](/docs/edge-compute/cloudfs/mount#formatting-a-needs_format-filesystem), then mount. ## Two Credentials, One API Key Putting the lanes together, a mounting client holds exactly what it needs and nothing more: | Credential | Reaches | Where you get it | |---|---|---| | `TELNYX_API_KEY` | The data lane (S3 access-key) | You already have it | | `meta_url` + `meta_token` | The metadata lane | Returned by **create** and **rotate** only | The point of splitting the credentials this way is that **the client never holds a raw database credential**. It talks to the metadata endpoint with a managed `meta_token` that Telnyx can rotate independently of the underlying metadata database, and it never connects to the database server directly. Your API key, meanwhile, only ever reaches object storage. Compromising a mount host exposes a rotatable metadata token and your (already-scoped) API key — not standing database credentials. Create and rotate are the **only** responses that return the `meta_token` (and the token-bearing `meta_url`). `GET` detail returns `meta_url` **without** the token, and list returns neither. Store the token when you create the filesystem — there is no way to read it back. ## Regions You choose a **data region** at create time: `us-central-1`, `us-east-1`, or `us-west-1`. That region is required — there is no default — and it determines the S3 endpoint (`s3_endpoint`) and where the file blocks physically live. It is the only region choice you make. **Metadata is always in us-east-1.** Picking `us-west-1` for data does not move the metadata database; that filesystem's metadata host is still `us-east-1.telnyxcloudfs.com`. Mount as close to us-east-1 as your data region allows if metadata latency matters to your workload. ## The Metadata Token Lifecycle The `meta_token` is the one credential in the system designed to be rolled. Rotation is a control-plane action: `POST /v2/storage/cloudfs/{id}/actions/rotate-meta-token` (Idempotency-Key required) issues a **new** `meta_token` and returns a new token-bearing `meta_url`. Rotation touches **only the token** — the metadata database and the S3 bucket are unchanged, so no data moves and no files are re-encrypted or re-keyed. The cutover is connection-scoped, which makes rotation safe to run against a live filesystem: - The **old token stops authenticating on the next metadata connection** — any *new* `juicefs mount` (or reconnect) must use the new `meta_url`. - An **already-mounted client is unaffected** on its existing connection. It keeps working with the connection it opened before rotation; you only need the new token the next time it reconnects. So the operational rule is: rotate, then update wherever the old `meta_url` is stored (secrets, deploy config) before your mounts next reconnect. ## Filesystem Status Each filesystem carries a `status`: | `status` | Meaning | |---|---| | `provisioning` | Create is in progress. Create is synchronous (up to a 300s timeout) and normally returns `ready` directly, so this state is mostly visible when listing during a create. | | `ready` | Fully provisioned and mountable. | | `needs_format` | The bucket and metadata database were provisioned, but the volume was never formatted. Mounting fails with `database is not formatted`; run `juicefs format` once first — see [Mounting](/docs/edge-compute/cloudfs/mount#formatting-a-needs_format-filesystem). | | `deleting` | Delete is in progress. | | `failed` | The last lifecycle action failed; the detail response carries an `error` string explaining why. | | `deleted` | Returned only by the delete response. A deleted filesystem no longer appears in list results and returns `404` on retrieval. | The steady states are `ready`, `needs_format`, and `failed`; `provisioning` and `deleting` are transitional. There is no soft-delete or trash lifecycle — `deleted` is terminal and permanent. ## Further Reading - [JuiceFS](https://github.com/juicedata/juicefs) — the open-source filesystem CloudFS is built on: [architecture](https://juicefs.com/docs/community/architecture) and [how JuiceFS stores files](https://juicefs.com/docs/community/architecture#how-juicefs-store-files). - OSTEP on the ideas CloudFS reuses: [Crash Consistency: FSCK and Journaling](https://pages.cs.wisc.edu/~remzi/OSTEP/file-journaling.pdf) — why the metadata index belongs in a transactional database — and [Log-structured File Systems](https://pages.cs.wisc.edu/~remzi/OSTEP/file-lfs.pdf) — write-new-never-overwrite, and the cleaner behind JuiceFS compaction. ## Next Steps - [Quick Start](/docs/edge-compute/cloudfs/quickstart) — Create a filesystem and mount it - [Mounting](/docs/edge-compute/cloudfs/mount) — The verified `juicefs mount` recipe - [Concurrent Access](/docs/edge-compute/cloudfs/concurrent-access) — Many clients mounting one filesystem, and file locking - [API Reference](/api-reference/cloudfs-filesystems/list-cloudfs-filesystems) — The six control-plane endpoints - [Overview](/docs/edge-compute/cloudfs) — What CloudFS is and when to use it --- ## Guides ### Mounting a Filesystem > Source: https://developers.telnyx.com/docs/edge-compute/cloudfs/mount.md A CloudFS filesystem is mounted with the [JuiceFS](https://juicefs.com) Community Edition client — there is no Telnyx-specific agent or daemon. The client talks to two backends directly: the metadata database (over a managed endpoint) and Telnyx Cloud Storage (over S3). You give it a metadata URL and a set of S3 credentials, and it exposes a POSIX filesystem at a mountpoint. This page is the end-to-end mount recipe, verified against a live filesystem. If you haven't created a filesystem yet, start with the [Quick Start](/docs/edge-compute/cloudfs/quickstart). For the two-lane architecture behind the credentials below, see [How CloudFS Works](/docs/edge-compute/cloudfs/concepts/how-cloudfs-works). ## Prerequisites - **The JuiceFS Community Edition client.** Install it per the [JuiceFS docs](https://juicefs.com/docs/community/getting-started/installation) and check with `juicefs version`. This recipe was verified end-to-end with JuiceFS CE 1.4.0. - **A Linux host or container with FUSE** (a usable `/dev/fuse`). JuiceFS mounts through FUSE. - **A created CloudFS filesystem**, and the `meta_url` and `meta_token` from its create response. The token is returned **only** at create (and on rotate) — if you didn't store it, you cannot reconstruct it and must rotate to get a new one. Running JuiceFS inside a Linux container is the most portable way to mount — it needs no FUSE install on the host and behaves identically everywhere, which is why the walkthrough below uses it. You can also mount natively on any Linux or macOS host that has FUSE (macOS requires [macFUSE](https://macfuse.github.io/)). ## Credentials the Client Needs The client holds three things. None of them is a raw database password — the metadata token *is* the password, embedded in the URL. | Variable | Where it comes from | Used for | |---|---|---| | `TELNYX_API_KEY` | Your Telnyx account | Doubles as the **S3 access key** for the data lane | | `META_URL` | The `meta_url` field of the create/get response | The metadata connection (metadata endpoint) | | `meta_token` | The `meta_token` field of the create/rotate response | The connection password, carried inline in `META_URL` | The `meta_url` returned by **create** already has the token inline as the password: ``` postgres://fs_:cloudfs_tok_...@us-east-1.telnyxcloudfs.com:5432/fs_?sslmode=require ``` The `meta_url` returned by **`GET /v2/storage/cloudfs/{id}`** is the same string **without** the token: ``` postgres://fs_@us-east-1.telnyxcloudfs.com:5432/fs_?sslmode=require ``` If you're working from the tokenless URL, splice your stored `meta_token` in as the password (`postgres://fs_:@...`). The host is always the region metadata host `us-east-1.telnyxcloudfs.com` — metadata is centralized there regardless of the filesystem's data region. ## Set the Environment ```bash export TELNYX_API_KEY="KEY..." # your account API key export META_URL="postgres://fs_:cloudfs_tok_...@us-east-1.telnyxcloudfs.com:5432/fs_?sslmode=require" # S3 credentials for the data lane. The access key is your API key. # The secret key's signature is ignored by Telnyx Storage — but it must be non-empty. export AWS_ACCESS_KEY_ID="$TELNYX_API_KEY" export AWS_SECRET_ACCESS_KEY="placeholder" ``` CloudFS volumes are formatted without embedded object-storage credentials, so JuiceFS picks them up from `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` at mount time. Set them explicitly. Telnyx Cloud Storage authenticates on the **access key alone** and ignores the SigV4 signature, so the secret can be any non-empty string — see [Cloud Storage authentication](/docs/cloud-storage/authentication). An empty secret fails: JuiceFS's AWS SDK rejects it with `static credentials are empty`. Do **not** run `juicefs format` against a filesystem whose status is `ready`. Provisioning already formats the volume (the bucket ships with a `juicefs_uuid` object and a fixed volume name), and re-formatting fails with `cannot update volume name`. The one exception is a filesystem in `needs_format` — see [Formatting a needs_format filesystem](#formatting-a-needs_format-filesystem). ## Mount Mount in the background and write logs to a file so the mount doesn't block your terminal. ```bash mkdir -p /mnt/agentfs juicefs mount --no-usage-report --background --log /tmp/juicefs.log "$META_URL" /mnt/agentfs ``` The first positional argument is the metadata URL; the second is the mountpoint. On success the process returns immediately and the filesystem is live at `/mnt/agentfs`. Watch `/tmp/juicefs.log` for the mount status and any warnings. The `--no-usage-report` flag opts out of JuiceFS's [anonymous usage reporting](https://github.com/juicedata/juicefs#usage-tracking). By default the JuiceFS client reports core metrics (such as its version) to the JuiceFS project on mount; it does not include user data. These docs pass `--no-usage-report` on every mount so CloudFS filesystems don't phone home by default — drop the flag if you'd like to share usage data with the upstream project. ### Formatting a `needs_format` Filesystem If `GET /v2/storage/cloudfs/{id}` reports `"status": "needs_format"`, the bucket and metadata database exist but the volume was never formatted, and mounting fails with `database is not formatted, please run juicefs format ... first`. This is the one case where you run `juicefs format` — once, then mount as normal: ```bash # Only for status: needs_format. The volume name is the s3_bucket value. juicefs format --storage s3 \ --bucket "/" \ "$META_URL" ``` `` and `` come from the filesystem's detail response, and the JuiceFS volume name **must** be the `s3_bucket` value (`cloudfs-fs-`) — any other name is rejected. Keep `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` exported (`format` probes the bucket over S3) and leave `--access-key` / `--secret-key` unset: the stored volume config then carries no credentials, same as a factory-formatted filesystem. Note that `--no-usage-report` is a `mount` flag — `format` doesn't accept it. ## In a Container (Docker) The container needs FUSE and the `juicefs` binary. Run it privileged and pass the FUSE device through with `--device /dev/fuse`. ```bash docker run --rm -it \ --privileged \ --device /dev/fuse \ -e TELNYX_API_KEY="$TELNYX_API_KEY" \ -e META_URL="postgres://fs_:cloudfs_tok_...@us-east-1.telnyxcloudfs.com:5432/fs_?sslmode=require" \ ubuntu:24.04 bash ``` Inside the container, install the client and mount: ```bash apt-get update && apt-get install -y curl fuse # use fuse3 if fuse is unavailable curl -sSL https://d.juicefs.com/install | sh - # installs the JuiceFS CE client juicefs version # verified with 1.4.0 export AWS_ACCESS_KEY_ID="$TELNYX_API_KEY" export AWS_SECRET_ACCESS_KEY="placeholder" mkdir -p /mnt/agentfs juicefs mount --no-usage-report --background --log /tmp/juicefs.log "$META_URL" /mnt/agentfs ``` `--privileged` and `--device /dev/fuse` are what let FUSE mount inside the container. Without them the mount fails to open `/dev/fuse`. If you bake the `juicefs` binary and `fuse` into your own image, you can skip the install step and go straight to `juicefs mount`. ## Verify the Mount ```bash # Confirm the mountpoint is a real mount mountpoint /mnt/agentfs # CloudFS reports a very large logical capacity (~1.0 PB) df -h /mnt/agentfs # Round-trip a file echo "hello cloudfs" > /mnt/agentfs/hello.txt cat /mnt/agentfs/hello.txt # Larger read/write test — write 10 MiB, read it back dd if=/dev/urandom of=/mnt/agentfs/blob bs=1M count=10 md5sum /mnt/agentfs/blob ``` Anything you write is chunked into 4 MiB block objects in the filesystem's Cloud Storage bucket and persists across unmount/remount. A normal filesystem workflow works on top of it — for example `git init` and `git commit` inside the mountpoint behave as expected. ## Unmount ```bash juicefs umount /mnt/agentfs ``` Unmounting only tears down the local FUSE mount; it does not touch the metadata database or the bucket. Remounting the same `META_URL` brings all files back. ## Troubleshooting | Symptom | Cause | Fix | |---|---|---| | `static credentials are empty` at mount | `AWS_SECRET_ACCESS_KEY` is empty | Set it to any non-empty placeholder — the signature is ignored, but the value can't be blank | | Mount succeeds, but writes hang for ~5 minutes and then fail (`flush … timeout`, `PutObject … exceeded maximum number of attempts` in the log), leaving 0-byte files | `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` missing or misnamed — mounting only talks to the metadata lane, so it comes up without S3 credentials; the data flush is what needs them | Export both variables, exact names, in the environment of the mount process (inside the container, not just on the host) and remount | | `database is not formatted` at mount | The filesystem's status is `needs_format` | [Format it once](#formatting-a-needs_format-filesystem), then mount | | `cannot update volume name` | You ran `juicefs format` against a `ready` (already-formatted) filesystem | Don't — `format` is only for `needs_format`. Otherwise, only `mount` | ## Next Steps - [Concurrent Access](/docs/edge-compute/cloudfs/concurrent-access) — mount the same filesystem from many clients at once, and coordinate writers with locks - [How CloudFS Works](/docs/edge-compute/cloudfs/concepts/how-cloudfs-works) — the metadata and data lanes, and what lives where - [API Reference](/api-reference/cloudfs-filesystems/list-cloudfs-filesystems) — create, list, get, update, delete, and rotate the metadata token - [Overview](/docs/edge-compute/cloudfs) — what CloudFS is and when to reach for it --- ### Concurrent Access > Source: https://developers.telnyx.com/docs/edge-compute/cloudfs/concurrent-access.md CloudFS is a **shared** filesystem: the same filesystem can be mounted by many clients at once — a fleet of agents, a set of containers, several hosts — and they read and write it concurrently. Because the metadata index lives in [one consistent store](/docs/edge-compute/cloudfs/concepts/how-cloudfs-works) that every client reads and writes directly (rather than each client keeping its own copy and reconciling later), all mounts converge on the same directory tree. ## Mount the Same Filesystem on Many Clients Every client mounts with the **same** `meta_url` (token included) and the **same** `TELNYX_API_KEY`. There is nothing per-client to set up — a mount holds no server-side state — so you can add or remove clients at any time. Run the [mount recipe](/docs/edge-compute/cloudfs/mount) on each host: ```bash # on every client — same META_URL, same API key, any mountpoint juicefs mount --no-usage-report --background --log /tmp/juicefs.log "$META_URL" /mnt/shared ``` Distribute the token to each client however you manage secrets. If you [rotate the token](/docs/edge-compute/cloudfs/concepts/how-cloudfs-works#the-metadata-token-lifecycle), already-mounted clients keep working on their existing connection; only new mounts need the new `meta_url`. ## What One Client Sees of Another's Writes CloudFS gives **close-to-open** consistency, the standard for shared filesystems: when a client writes a file and closes it, other clients see the new contents the next time they open it. Directory operations — create, rename, delete — commit to the metadata store immediately and become visible to other clients within about a second (the lifetime of a client's kernel metadata cache). With client **A** and client **B** both mounted at `/mnt/shared`: ```bash # on A — write a file and close it echo "hello from A" > /mnt/shared/from-a.txt # on B — read it back cat /mnt/shared/from-a.txt # -> hello from A ls /mnt/shared # -> from-a.txt (plus anything other clients wrote) ``` Concurrent writes to **different** files are independent: many clients can each write their own files at the same time with no coordination, and every client reads back exactly what another wrote, byte-for-byte. ## Coordinating Writers on the Same File Two clients writing the **same** file (or the same byte range) at the same time is the one case that needs coordination — as on any shared filesystem, uncoordinated overlapping writes resolve last-writer-wins. Use file locks: CloudFS supports both **BSD locks** (`flock`) and **POSIX record locks** (`fcntl`), and JuiceFS coordinates them **across clients** through the shared metadata. A lock held on one host blocks a conflicting lock on another. ```bash # on A — hold an exclusive lock while appending to a shared worklog flock -x /mnt/shared/worklog.lock -c 'echo "$(date -u) A: built module X" >> /mnt/shared/worklog' # on B — the same lock; B waits until A releases (here, up to 5s) flock -x -w 5 /mnt/shared/worklog.lock -c 'echo "$(date -u) B: ran tests" >> /mnt/shared/worklog' ``` If B can't acquire the lock within its timeout, `flock` exits non-zero and B's command doesn't run — so a fleet can serialize access to a shared resource (a worklog, a build output directory, a leader-election file) without any external coordinator. ## Worked Example: Two Clients, One Filesystem Two containers, each an independent client, mounting the same filesystem: ```bash # build a JuiceFS-ready image once docker build -t cfs-client - <<'EOF' FROM debian:bookworm-slim RUN apt-get update && apt-get install -y curl ca-certificates fuse3 \ && curl -sSL https://d.juicefs.com/install | sh - EOF # start two clients (each needs FUSE) docker run -d --name client-a --privileged --device /dev/fuse cfs-client sleep infinity docker run -d --name client-b --privileged --device /dev/fuse cfs-client sleep infinity # mount the SAME filesystem in each (same META_URL + API key) for c in client-a client-b; do docker exec -e META_URL="$META_URL" \ -e AWS_ACCESS_KEY_ID="$TELNYX_API_KEY" -e AWS_SECRET_ACCESS_KEY="placeholder" "$c" \ sh -c 'mkdir -p /mnt/shared && juicefs mount --no-usage-report --background --log /tmp/j.log "$META_URL" /mnt/shared' done # A writes, B reads it docker exec client-a sh -c 'echo "written by A" > /mnt/shared/hello.txt' docker exec client-b sh -c 'cat /mnt/shared/hello.txt' # -> written by A ``` ## Caveats - **Same-file, uncoordinated concurrent writes** resolve last-writer-wins on overlapping regions. Use the locks above whenever more than one client may write the same file. - **Metadata latency.** Every metadata operation — open, create, rename, lock — is a round-trip to `us-east-1` (see [The Metadata Lane](/docs/edge-compute/cloudfs/concepts/how-cloudfs-works#the-metadata-lane)). Coordination-heavy or many-small-file workloads run faster the closer clients mount to us-east-1; bulk data throughput is unaffected. - **One credential set per filesystem.** All clients share the same `meta_token` and API key; there is no per-client scoping *within* a filesystem. ## Next Steps - [Mounting a Filesystem](/docs/edge-compute/cloudfs/mount) — the full mount recipe each client runs - [How CloudFS Works](/docs/edge-compute/cloudfs/concepts/how-cloudfs-works) — why one consistent metadata store makes shared access work - [Quick Start](/docs/edge-compute/cloudfs/quickstart) — create a filesystem and mount it ---