tools like runloop ? Specifically for " i have datasets i want to host on a filesystem somewhere and i want to spin up arbitrary computers running claude code / codex cli with read only access to those files" how would you do this ?
Based on a real day; names and selected personal details have been changed or omitted.
Hosting Datasets with Runloop
I’ll treat this as an architecture question plus a “what hosted sandbox tools exist right now” question. I’ll verify current Runloop-style products, then give the practical build path.
Yes. I’d build this as object storage + ephemeral agent sandboxes + a writable scratch disk, not as “one giant shared Linux filesystem.”
The shape
R2/S3/GCS dataset bucket
└── mounted/read-only or synced into sandboxes
Orchestrator API
├── creates sandbox
├── injects task + model keys
├── mounts dataset read-only
├── runs claude/codex CLI
├── streams logs
└── saves result artifacts back to bucket/db
Agent sandbox
├── /data read-only dataset
├── /workspace writable scratch / repo clone
├── /tmp disposable
└── network restricted
The important trick: do not make the agent’s working directory read-only. Claude Code / Codex-style tools want to write plans, temp files, caches, patches, etc. Give them /workspace as writable, and mount your corpus at /data read-only.
Use Runloop if you want the cleanest “coding agent box”
Runloop’s thing is basically this: secure cloud “Devboxes” for AI agents, with VM isolation and a full execution environment. Their docs describe Devboxes as sandboxed environments for agents, and the product is explicitly positioned around launching agents in secure code sandboxes. citeturn370428search1turn370428search4
For your use case:
Build a custom devbox image with:
claudecodexripgreppythonuvnode- your search/index tooling
- maybe
duckdb,sqlite,jq,fd
On job start:
- create devbox
- mount or download dataset shard to
/data - mark
/dataread-only - run
claude --dangerously-skip-permissionsonly inside the sandbox, or safer equivalent with sandbox/network limits - stream stdout/stderr
- persist
/workspace/results
Destroy or snapshot the box.
Runloop is probably best if your unit of work is: “spin up a remote coding computer, let an agent inspect files, write scripts, iterate, and produce an output.”
Alternatives
E2B: good if you want fast API-created sandboxes. E2B says it provides isolated sandboxes where agents can execute code, run commands, and interact with the filesystem; it also has docs specifically for running Claude Code, Codex, and Amp in secure sandboxes. citeturn673054search0turn673054search20
Daytona: similar category, maybe more “full composable computer.” Daytona describes sandboxes with dedicated kernel, filesystem, network stack, vCPU, RAM, and disk, and exposes lifecycle/filesystem/process APIs. citeturn673054search5turn673054search1
Modal: better if you think of this as scalable batch compute rather than “agent devboxes.” Modal has Sandbox for running untrusted/agent code and Volumes optimized for write-once/read-many workloads. It also supports cloud bucket mounts for S3/R2/GCS-style storage. citeturn673054search18turn673054search10turn673054search6
Morph Cloud: interesting if you care about snapshot/fork/resume devboxes. Morph positions Devboxes as programmable cloud development environments for CLI agents like Claude Code, Codex, and Gemini CLI. citeturn673054search7turn673054search15
What I’d actually do for you
Given your “huge corpus / Alpha Research / agentic knowledge workers” thing:
V0: cheap and good
Use Cloudflare R2 for data, Runloop or Modal for compute, and Postgres/D1 for jobs.
Dataset layout:
r2://alpha-corpora/
gutenberg/
raw/
normalized/
metadata.parquet
shards/
shard-00000.jsonl.zst
shard-00001.jsonl.zst
gov-archives/
newspapers/
Each job gets:
/data
/manifest.json
/shards/shard-00042.jsonl.zst
/workspace
/task.md
/scratch.py
/notes.md
/answer.md
Your orchestrator creates jobs like:
type AgentJob = {
id: string
model: "claude-code" | "codex"
prompt: string
datasetRefs: string[]
allowedCommands?: string[]
networkMode: "off" | "allowlist" | "open"
maxRuntimeSeconds: number
}
For most research tasks, do not mount the whole corpus. Give the agent:
- a manifest
- metadata DB/parquet
- a retrieval CLI
- selected shards
- read-only raw data
The agent should call tools like:
search-corpus "sierra club 2024 lawsuit energy campaign"
open-doc gutenberg/12345
sample-shard newspapers/nyt-1982 --n 100
duckdb metadata.duckdb -c "select ..."
This is much better than letting Claude Code wander a 20 TB filesystem.
Read-only dataset mechanics
Best options, ranked:
Object store + signed read-only credentials
- The sandbox can only read
s3:GetObject,ListBucketfor a prefix. - No write/delete permissions.
- Best security boundary.
- The sandbox can only read
Mount object store read-only
- S3/R2 via Mountpoint, FUSE, provider-native mount.
- Convenient, but watch perf and weird filesystem semantics.
Pre-copy selected shards into the sandbox
- Slow for huge jobs, but simplest.
- Good for V0.
Network file system
- EFS/NFS/CephFS.
- Useful if you need POSIX semantics.
- More ops burden, more security risk.
For agent jobs, I’d prefer object storage + local scratch. Filesystems are nice until 200 agents start recursively grepping the same shared mount.
Security rules that matter
You want three separate boundaries:
Filesystem
/dataread-only/workspacewritable- no host mounts
- no home directory secrets
- no Docker socket
- no cloud metadata endpoint
Network
- ideally off by default
- allow only model API + object store + your orchestrator
- block arbitrary outbound internet if working over private datasets
Credentials
- per-job short-lived creds
- read-only dataset creds
- write-only result upload path
- no global R2/S3 key in env
Anthropic’s Claude Code sandboxing docs/blog emphasize exactly this kind of split: filesystem isolation plus network isolation, with configurable allowed paths and domains. citeturn370428search3turn370428search24
The “agent can’t work on read-only folders” gotcha
Do not run Claude Code with its current directory inside /data. Keep it in /workspace and tell it:
The dataset is mounted at /data and is read-only.
Do not attempt to modify files in /data.
Write scripts, notes, indexes, and outputs under /workspace.
There are current reports/issues around read-only sandbox modes behaving unexpectedly or not matching what users expect, so I would enforce read-only at the mount/credential/container layer, not rely only on the agent CLI’s own sandbox flag. citeturn370428search18turn370428search6
My preferred stack
For fastest build:
Runloop + R2 + small orchestrator
- Runloop: agent computer
- R2: dataset storage
- D1/Postgres: job table
- Worker/Hono: job API
- Durable Object: stream logs / job status
rg,duckdb,python, customsearch-corpusCLI inside image
For cheaper/high-scale batch:
Modal + R2/S3 + custom CLI agent runner
- Modal sandboxes/functions for elastic compute
- Volumes or CloudBucketMounts for data
- Better for “run 10,000 corpus jobs”
- Less ideal if you need interactive Claude Code-style devbox behavior
For fully self-hosted:
Kubernetes + Firecracker/Kata/gVisor + S3
- Harder, but gives control.
- Each job is a pod/microVM.
- Mount
/dataread-only from S3/FUSE or prefetch. - Run agent as non-root.
- Use NetworkPolicy to restrict egress.
Minimal V0 implementation
- Put corpus in R2.
- Create a Docker image:
FROM node:22-bookworm
RUN apt-get update && apt-get install -y \
ripgrep fd-find jq python3 python3-pip pipx curl unzip git duckdb \
&& rm -rf /var/lib/apt/lists/*
RUN npm install -g @anthropic-ai/claude-code
# install codex cli however you're using it
WORKDIR /workspace
- On each job:
- create sandbox from image
- inject
ANTHROPIC_API_KEYor OpenAI key - inject short-lived read-only R2 credentials
- copy
task.mdinto/workspace - either mount or sync selected dataset prefix into
/data - run:
cd /workspace
claude -p "$(cat task.md)" \
--allowedTools "Bash,Read,Write,Edit,Grep,Glob"
Or for Codex:
cd /workspace
codex exec --sandbox workspace-write "$(cat task.md)"
- Upload:
/workspace/answer.md/workspace/artifacts/*- logs
- tool call transcript if available
The non-obvious improvement
Give agents a corpus tool, not just files.
Instead of:
“Here is 5 TB mounted at
/data, figure it out.”
Give:
corpus search "query" --dataset sierra-club --top-k 100
corpus open doc_id
corpus grep "phrase" --dataset ngo-filings
corpus sql "select ..."
corpus sample --where "year=2024 and source='irs_990'"
Then Claude/Codex can write code when needed, but most work becomes high-signal corpus interrogation. This also lets you log every read and prevent accidental exfiltration.
Bottom line
Use Runloop if you want “remote Claude Code/Codex computers.” Use Modal if you want scalable batch compute over datasets. Use R2/S3 as the source of truth, mounted or copied read-only into each sandbox. Always give the agent a writable /workspace; enforce read-only with storage credentials/mount flags, not just the agent’s own settings.
does runloop have permanent storage that can be quickly mounted to arbitrary devboxes ?
Probably not in the exact “persistent shared volume instantly attachable to arbitrary devboxes” sense.
What Runloop clearly has:
Devbox disk snapshots
You can save the current disk state of a Devbox and create new Devboxes from that snapshot. Their docs say snapshots are for saving disk state, improving build times, rolling back, and fan-out attempts. They also say “currently only disk snapshots are supported.” citeturn273130search2Files/build-context mounts for building blueprints
Their files/mounts docs mention build contexts stored as temporary Storage Objects with TTLs. That sounds more like “upload files into the build context / image build process,” not a reusable permanent dataset volume mounted into arbitrary running devboxes. citeturn273130search1
So: Runloop gives you reusable snapshots, not obviously a first-class EFS-style persistent volume service.
For your dataset use case, I would not rely on Runloop-native storage as the corpus layer. I’d do:
R2/S3/GCS = permanent dataset store
Runloop snapshot = prebuilt agent environment
Runloop devbox = ephemeral worker
/data = read-only mounted/synced dataset shard
/workspace = writable agent scratch
The best pattern:
Base snapshot:
claude/codex/ripgrep/duckdb/python/node installed
your corpus CLI installed
no giant dataset inside
Per job:
launch devbox from snapshot
attach short-lived read-only R2/S3 creds
mount or sync needed dataset prefix to /data
run agent
upload outputs
destroy devbox
If you want true quickly mountable persistent volumes, I’d look harder at Modal Volumes / CloudBucketMounts, Daytona, Deno Sandbox Volumes, or self-hosted EFS/NFS/CephFS + Firecracker/K8s. Runloop seems strongest for “agent devbox lifecycle + snapshots,” less clearly for “giant shared read-only corpus volume.”
can you mount an s3 bucket / r2 like a filesystem ??
Yes. You can mount S3/R2 “like a filesystem,” but it’s a FUSE/object-store filesystem, not a real POSIX filesystem.
Best options:
1. rclone mount — best for Cloudflare R2
Cloudflare officially documents using rclone with R2, and rclone supports S3-compatible providers including R2. R2 exposes an S3-compatible API endpoint like https://worker.example.invalid/example-job-0001. citeturn700635search1turn700635search4turn700635search9
Example:
rclone config
# choose: s3
# provider: Cloudflare
# endpoint: https://worker.example.invalid/example-job-0001
# access_key_id / secret_access_key
Then:
mkdir -p /data/r2
rclone mount r2:my-bucket /data/r2 \
--read-only \
--vfs-cache-mode full \
--dir-cache-time 1h \
--poll-interval 0
For your agent boxes, this is probably the easiest:
rclone mount r2:alpha-corpora /data \
--read-only \
--vfs-cache-mode full \
--cache-dir /workspace/.rclone-cache
Then Claude/Codex sees:
/data/gutenberg/...
/data/irs-990/...
/data/newspapers/...
2. AWS Mountpoint for S3 — best for actual AWS S3
AWS has Mountpoint for Amazon S3, a high-throughput open-source client that mounts an S3 bucket locally and translates file operations like open and read into S3 API calls. It intentionally does not implement full POSIX filesystem semantics. citeturn700635search0turn700635search3
Example:
mkdir ~/mnt
mount-s3 my-bucket ~/mnt
With cache:
mount-s3 --cache /tmp/s3-cache my-bucket /data
This is probably better than rclone for high-throughput AWS S3 reads, especially ML/dataset-style workloads. AWS notes caching is useful when repeatedly reading unchanged training-style datasets. citeturn700635search0
For R2, I’d expect rclone to be less annoying.
Important limitations
Object storage mounted as a filesystem is good for:
cat file
ls directory
read large files
stream parquet/jsonl/csv
grep selected paths
copy files out
It is bad for:
millions of tiny random reads
sqlite directly over mount
lots of metadata-heavy directory walking
atomic renames
file locking
append-heavy writes
POSIX correctness
agent writing into the mounted corpus
So don’t do:
cd /data
claude
Do:
cd /workspace
claude
# read from /data, write to /workspace
Best pattern for your use case
/data rclone-mounted R2 bucket, read-only
/workspace local writable disk
/cache local cache for repeated reads
/results uploaded back to R2/S3 when done
And mount with per-job read-only credentials. The bucket policy / API token should literally lack write/delete permissions. The --read-only flag is nice, but credentials are the real boundary.
My recommendation
For Runloop/devboxes:
apt-get install -y fuse3
curl https://rclone.org/install.sh | bash
mkdir -p /data /workspace/.rclone-cache
rclone mount r2:alpha-corpora /data \
--read-only \
--vfs-cache-mode full \
--cache-dir /workspace/.rclone-cache \
--dir-cache-time 6h \
--daemon
Then prompt the agent:
Dataset is mounted read-only at /data.
Write all scripts, notes, indexes, and outputs under /workspace.
Never modify /data.
For huge corpora, I’d still add a corpus search CLI on top. Let the agent search first, then open specific files from /data, rather than recursively crawling the whole mounted bucket.
but will rclone clone the data ? I need the mount to be instant. and i want to treat it as files on a unix system.
No, rclone mount does not clone the whole bucket. The mount can appear basically instantly. It lazily fetches metadata/data as files are listed/read. With VFS caching, it caches chunks or files locally as they are accessed, but it does not pre-download everything unless you explicitly script that. citeturn130405search0turn130405search3
But the catch: it is not a real Unix filesystem. It is “S3/R2 object storage presented through a filesystem interface.”
What works well
ls /data
cat /data/foo.txt
python script.py /data/shard-001.jsonl
duckdb -c "select * from read_parquet('/data/foo.parquet')"
rg "phrase" /data/some-prefix
cp /data/file /workspace/file
What gets weird / bad
sqlite /data/db.sqlite # bad
git repo living on /data # bad
mv/rename lots of files # bad
chmod/chown expectations # bad
file locking # bad
millions of tiny file stats # slow/expensive
recursive crawl of huge bucket # slow/expensive
AWS Mountpoint explicitly says it supports basic file operations over S3 but does not provide full POSIX semantics: no symlinks, no file locking, limited modification/delete behavior, etc. citeturn130405search2turn130405search5
For your exact use case
You can absolutely do:
/data # instant-ish mounted R2/S3 read-only corpus
/workspace # real local Unix disk for Claude/Codex to write into
Then run:
cd /workspace
claude
# or codex
And tell the agent: read from /data, write to /workspace.
R2 mount command
Something like:
mkdir -p /data /workspace/.rclone-cache
rclone mount r2:my-corpus-bucket /data \
--read-only \
--vfs-cache-mode full \
--cache-dir /workspace/.rclone-cache \
--dir-cache-time 6h \
--poll-interval 0
Cloudflare documents using rclone with R2 via S3-compatible access keys. citeturn130405search1turn130405search7
But for “treat it as files on Unix”
There are two levels:
Good enough file interface: rclone mount, mount-s3, s3fs, goofys.
Actually Unix/POSIX-like shared filesystem: EFS, NFS, CephFS, JuiceFS, SeaweedFS, Alluxio, Lustre, FSx, etc.
For agent research over mostly immutable datasets, I’d use:
R2/S3 object store
↓
rclone/mountpoint read-only at /data
↓
local NVMe /workspace
↓
results uploaded back to R2
For workloads that need real filesystem semantics, use JuiceFS or EFS/NFS backed by object storage. JuiceFS is probably the most interesting middle layer: object storage as backend, POSIX-ish mount interface, metadata in Redis/Postgres/MySQL, and mountable across many machines.
The real answer
For “instant mount, read-only corpus, arbitrary devboxes,” yes: rclone mount is fine.
For “I want agents/programs to behave as if /data is a normal Unix filesystem in every way,” no: use JuiceFS / EFS / NFS / CephFS.
I’d start with rclone mount until it breaks. It probably only breaks once agents start doing dumb recursive crawls or tools expect locking/mutation.
ok if i used juiceFS two questions:
- Can i have simultaneous workers on it? Scale it up forever (read only , there would be write locks)
- What would you use for the claude code workers ? Modal ? Runloop ? Something different ?
I’m going to separate the storage layer from the worker layer; the right answer is mostly “JuiceFS can do this, but metadata and cache topology decide whether it feels infinite.”
1. Can you have simultaneous workers on JuiceFS?
Yes. That is basically the point of JuiceFS.
JuiceFS is a distributed POSIX-ish filesystem where file data lives in object storage and metadata lives in Redis / Postgres / MySQL / TiKV / etc. Multiple machines can mount the same filesystem at once. JuiceFS says it can be mounted and accessed across different hosts, and its architecture separates object data from the metadata engine. citeturn394564search9turn394564search12
For your case — read-only corpus, many workers — it should work well.
The scaling bottlenecks are:
1. metadata engine
ls, stat, open, path traversal, file locks
2. object store throughput
actual file/chunk reads from S3/R2/GCS
3. per-worker local cache
whether each worker repeatedly refetches same blocks
4. directory/file layout
millions of tiny files are worse than larger shards/parquet/jsonl
JuiceFS clients read file blocks concurrently and use object storage as the backing data layer. citeturn394564search8 It also has metadata and data caching; for read-heavy workloads, their docs specifically mention --opencache and client-side file attribute/chunk mapping caches. citeturn394564search4
So the answer is:
Can I have simultaneous workers? Yes.
Can I scale it "forever"? No, but very far if read-only and well laid out.
For “scale it up forever,” the practical constraints are:
- Redis metadata is fine for moderate scale, but eventually you want TiKV / MySQL / Postgres / JuiceFS Cloud metadata, depending on ops tolerance.
- R2/S3 request/throughput limits and egress/network matter.
- If every worker recursively runs
find /dataorrgover the entire corpus, you will melt metadata/object-store performance. - If workers mostly read selected files/shards after search/indexing, you can scale very wide.
For read-only agent workers, mount it read-only:
juicefs mount \
--read-only \
--cache-dir /workspace/.juicefs-cache \
--cache-size 102400 \
--opencache \
redis://... \
/data
I would also design the corpus as:
/data/
manifests/
metadata.duckdb
indexes/
shards/
dataset=a/year=2024/part-000001.parquet
dataset=a/year=2024/part-000002.parquet
Not:
/data/
900 million tiny text files
Tiny files kill you because every open/stat/listing hits metadata.
Write locks?
JuiceFS metadata includes file locks and common filesystem metadata. citeturn394564search12 But for your workload I would avoid shared writes entirely.
Do:
/data JuiceFS read-only corpus
/workspace local writable disk per worker
/results object-store prefix or database upload
Do not let 1,000 Claude Code instances write into the same JuiceFS tree unless you really need it. Shared mutable filesystems plus agents is asking for stupid race conditions.
If you need writes, use this pattern:
/results/job-123/...
/results/job-124/...
/results/job-125/...
Append-only, per-job output directories. No shared mutable “current result” file.
2. What would I use for Claude Code workers?
My pick depends on whether you want agent devboxes or massively parallel jobs.
Best default: Modal
For your “many arbitrary computers running Claude Code / Codex CLI over a mounted corpus” thing, I’d lean Modal.
Why:
- Modal Sandboxes are explicitly for running untrusted/user/agent code in isolated containers. citeturn394564search10
- Modal supports Volumes and CloudBucketMounts with Sandboxes; their docs say Volumes and CloudBucketMounts let you upload data once and access it efficiently from many sandboxes. citeturn394564search17
- Modal Volumes are distributed and provide a filesystem interface. citeturn394564search14
- CloudBucketMount supports R2/S3/GCS-style buckets, but inherits Mountpoint/object-store limitations. citeturn394564search2turn394564search6
For pure compute orchestration, Modal is closer to what you want than Runloop:
submit 1,000 jobs
each gets container
mount /data
run claude/codex
stream logs
upload artifacts
scale down to zero
I’d use Modal if the UX is:
“Run this research/code task over this dataset shard and return artifacts.”
Best coding-agent product: Runloop
Runloop is better if the UX is:
“Give Claude Code a real remote devbox and let it hack interactively.”
Runloop Devboxes are secure VM-based execution environments for AI agents. citeturn394564search7 They also support snapshots to save disk state and create new devboxes from that state. citeturn394564search3
So Runloop is good for:
- coding tasks
- repo edits
- browser-ish/devbox workflows
- persistent-ish agent attempts
- fan-out from snapshots
- software engineering evals
But I’m less convinced it is ideal for your “mount the same giant read-only JuiceFS corpus into arbitrary workers” thing unless Runloop gives you clean FUSE privileges / custom mounting / long-running sidecars. You’d have to verify whether their devboxes allow the exact JuiceFS mount flow you need.
If using JuiceFS specifically: Kubernetes may be cleanest
If JuiceFS is central, the “boring correct” answer is:
Kubernetes
+ JuiceFS CSI driver
+ worker pods
+ per-job PVC/mount
+ local ephemeral disk
+ queue
JuiceFS has a CSI driver for Kubernetes, exposing JuiceFS as PersistentVolumes to pods. citeturn394564search20
Architecture:
R2/S3/GCS object storage
↑
JuiceFS
↑
K8s PersistentVolume mounted read-only at /data
↑
worker pods running claude/codex
This is the most “real infra” version. More annoying than Modal, but you get:
- exact mount control
- read-only mounts
- node-local caches
- sidecars
- network policies
- cheaper sustained scale
- no dependency on a sandbox vendor supporting FUSE weirdness
For a serious Alpha Research infra layer, I’d eventually end up here or on self-managed Firecracker.
My recommendation
Start with Modal + object-store mount or Modal Volume unless you’re absolutely set on JuiceFS.
If you need true POSIX-ish shared FS, use:
Modal worker containers
+ install juicefs in image
+ mount JuiceFS at /data read-only
+ run Claude/Codex in /workspace
But confirm Modal permits the needed FUSE mount behavior. If FUSE is a pain, use Modal’s own Volume/CloudBucketMount first.
If you want the cleanest long-term architecture:
Storage:
R2/S3 for raw objects
JuiceFS for POSIX-ish read-only corpus mount
Postgres/TiKV/managed JuiceFS metadata, not random tiny Redis forever
Workers:
V0: Modal
V1: Kubernetes + JuiceFS CSI
Special interactive coding boxes: Runloop
Decision table
| Need | Use |
|---|---|
| Fastest prototype | Modal Sandboxes |
| Claude Code interactive devbox | Runloop |
| Massive batch agent workers | Modal |
| Full control + JuiceFS done right | Kubernetes + JuiceFS CSI |
| Cheapest sustained high volume | Hetzner/DO/K8s/Fly Machines + JuiceFS |
| Huge read-only files, not POSIX | R2/S3 mount directly |
| True shared POSIX-ish corpus | JuiceFS |
For you, I’d do:
V0:
Modal workers
R2 bucket or Modal Volume
local /workspace
Claude/Codex CLI
results to R2
V1:
Add JuiceFS only once direct object-store mounts become too limiting
V2:
K8s + JuiceFS CSI if this becomes core infra
Runloop is attractive, but I’d reserve it for software engineering devboxes, not corpus-scale worker fleets.
modal with modal volumes or juiceFS ? can modal mount juiceFS ?
I’ll check the current Modal storage/mount constraints before giving a recommendation, because the answer depends on whether Modal’s sandbox/container runtime allows FUSE-style mounts cleanly.
Modal Volumes vs JuiceFS
Use Modal Volumes first.
Modal Volumes give you persistent shared filesystem storage that can be mounted into Modal Functions and Sandboxes. Modal says Volumes are distributed, cached/chunked for throughput, and expose a full filesystem interface. They also work directly with Sandboxes. citeturn382854search14turn382854search4
For your agent-worker use case:
/data Modal Volume, read-only corpus or prepared dataset
/workspace local sandbox disk, writable
/results Modal Volume or R2 output prefix
This is much simpler than bringing JuiceFS into Modal.
Can Modal mount JuiceFS?
Maybe, but I would assume not cleanly until proven otherwise.
JuiceFS’s normal mount path is FUSE. JuiceFS’s Docker docs say mounting directly inside a container requires --privileged because FUSE is used. citeturn382854search2 JuiceFS’s Kubernetes CSI setup also uses a privileged mount pod for FUSE. citeturn382854search8
Modal Sandboxes are secure containers for agent/user code, not arbitrary privileged Docker hosts. citeturn382854search9 So unless Modal explicitly supports FUSE/privileged mounts for your account/runtime, I would not design around juicefs mount inside Modal.
You might be able to use JuiceFS in Modal if:
1. Modal allows FUSE/device access in your container, or
2. JuiceFS is mounted outside the container and passed in, or
3. you use JuiceFS through an NFS/Samba gateway instead of FUSE
But those are more annoying than just using Modal Volumes.
What about Modal CloudBucketMount?
Also viable, especially for R2/S3.
Modal has CloudBucketMount, which supports AWS S3, Cloudflare R2, and Google Cloud Storage buckets. It is built on AWS Mountpoint tech and inherits object-store mount limitations. citeturn382854search1turn382854search6
So the hierarchy is:
Modal Volume
Best for: persistent shared filesystem inside Modal
Modal CloudBucketMount
Best for: cheap huge R2/S3 corpus, mostly sequential reads
JuiceFS
Best for: you need POSIX-ish shared filesystem across many non-Modal machines too
My recommendation
For V0:
Modal Sandboxes
Modal Volume for prepared corpus/indexes
CloudBucketMount or R2 for raw giant objects
local /workspace for Claude/Codex writes
Do not start with JuiceFS inside Modal.
A good layout:
/data/indexes Modal Volume
/data/manifests Modal Volume
/data/raw CloudBucketMount to R2, read-mostly
/workspace local writable disk
/results Modal Volume or R2 upload
Then run agents from /workspace, not /data.
When I’d choose JuiceFS instead
Use JuiceFS if you want the same filesystem mounted across:
Modal workers
Runloop devboxes
your own Hetzner/DO/Fly machines
Kubernetes pods
local dev machines
But in that world I’d probably move the workers to Kubernetes + JuiceFS CSI, not Modal. JuiceFS has a Kubernetes CSI driver specifically for mounting JuiceFS into pods. citeturn382854search5
Bottom line
For Modal: Modal Volumes + CloudBucketMounts.
For JuiceFS: Kubernetes/self-hosted workers.
Trying to force JuiceFS FUSE mounts inside Modal is likely the wrong first move.
can modal volumes work across multiple workers simultaneously ? Does this provide any advantage over my current digital ocean setup ? Modal
Yes, Modal Volumes can be mounted across multiple workers simultaneously.
Modal describes Volumes as a writable filesystem that can be shared between one or more Modal functions, and used to persist durable state across several instances of the same function. They also work with Sandboxes, so many Claude/Codex sandboxes can access the same mounted Volume. citeturn953528search1turn953528search7
But there’s a big semantic caveat:
Modal Volumes are not quite “live NFS”
For regular Modal Volumes, changes from one container may not instantly appear in another container. In Sandbox docs, Modal says Volume files are synced back when the Sandbox terminates; CloudBucketMounts sync automatically. citeturn953528search7
So for your use case, Modal Volumes are great for:
/data read-mostly corpus/index/model files
/workspace local writable scratch
/results per-job output, synced/uploaded at end
They are not ideal for:
100 agents collaboratively editing the same shared tree live
agents depending on immediate cross-worker file visibility
shared SQLite DB
append-heavy logs from many workers into same files
For read-only datasets, this is fine. The workers all see the same mounted corpus. Just do not use the Volume as a coordination database.
Advantage over your DigitalOcean setup
Probably yes, if your bottleneck is bursting arbitrary agent workers.
Modal advantages:
- You don’t manage machines
- Scales to many containers quickly
- Built-in sandbox/function abstraction
- Persistent Volumes
- Easy Python orchestration
- Good for bursty batch jobs
- Per-job CPU/GPU/memory selection
- No idle server cost
Modal is explicitly optimized for large-scale batch processing and can scale jobs across many containers without you hand-managing workers. citeturn953528search3turn953528search10
DigitalOcean advantages:
- Cheaper if machines are busy 24/7
- Simpler mental model: real VM, real disk
- More control over Docker/FUSE/systemd/networking
- Easier to mount JuiceFS/NFS however you want
- Better for long-lived services
- Less vendor-specific
The key distinction
If you currently have a DO box with:
1 big disk
1 queue
N worker processes
datasets already local
Modal is not magically better. It is mostly better when you want:
10 workers now
0 workers in 5 minutes
500 workers for one big corpus job
different job sizes
ephemeral isolated agent sandboxes
easy fanout
What I’d use
For your exact thing:
Modal Volume:
prepared indexes, manifests, smaller hot datasets
Cloudflare R2 / S3:
raw giant corpus
Modal CloudBucketMount:
mount raw R2/S3 data when needed
Local sandbox disk:
/workspace for Claude/Codex writes
DB/queue:
job status, claims, locks, outputs metadata
I would not use Modal Volume as the main source of truth for huge raw corpora unless the data is already in Modal and mostly static. For giant datasets, keep R2/S3 as source of truth and use Modal Volume for hot/cache/index layers.
My call
Use Modal if your goal is: “spin up arbitrary Claude/Codex workers on demand over a mostly read-only dataset.”
Stay on DigitalOcean if your workload is: “a few always-on workers, one local corpus, predictable load.”
Best hybrid:
DigitalOcean:
orchestrator, DB, dashboards, maybe corpus indexing
R2/S3:
canonical dataset
Modal:
bursty Claude/Codex workers
Modal Volume:
hot indexes / manifests / reusable environment data
That gives you the benefit of Modal without moving your entire infra religion into it.
Right now my data lives in a digital ocean volume. What's wrong with a modal volume ? it's not that big (1 TB)
Nothing is inherently wrong with a Modal Volume for 1 TB. For your use case it may be the cleanest move.
The main caveat is semantic, not size.
Modal Volume is good if your data is mostly read-only
For:
1 TB corpus
many Claude/Codex workers
read shared files
write per-job outputs elsewhere
Modal Volume is probably fine.
Modal describes Volumes as writable persistent filesystems shareable between functions/instances, exposed as a filesystem. citeturn354233search4 They are also designed for distributed, on-demand compute, where Modal can scale jobs across many containers. citeturn354233search6
So your architecture could simply be:
Modal Volume: /data # your 1 TB dataset
Local disk: /workspace # Claude/Codex scratch
R2/S3/Volume: /results # job outputs
Run agents from /workspace, read from /data.
What’s worse than your DigitalOcean Volume?
A DO volume is basically a normal block volume attached to a VM. It has simpler, more predictable filesystem semantics.
Modal Volume is more “distributed persistent filesystem for Modal containers.” The tradeoffs:
| Thing | DigitalOcean Volume | Modal Volume |
|---|---|---|
| Normal Linux filesystem feel | Better | Good, but platform-managed |
| Attach to many workers | Not really | Yes |
| Burst to hundreds of workers | You manage it | Modal handles it |
| Long-running always-on cheapness | Usually better | Maybe worse |
| Zero-idle burst jobs | Worse | Better |
| Works outside Modal | Yes | No |
| Weird sync/concurrency edge cases | Fewer | More likely |
| Infra control | Higher | Lower |
The biggest issue: don’t treat Modal Volume like a live multi-writer NFS coordination layer. Modal’s docs say Volumes can share/persist state, but for Sandboxes, Volume files are synced back when the Sandbox terminates; CloudBucketMounts sync automatically. citeturn354233search0 So if many Claude workers write to the same shared paths and expect immediate visibility, that is risky.
But for read-only data, this mostly does not matter.
What’s better than your DO setup?
Modal gives you:
- Many isolated workers without managing VMs
- Scale-to-zero
- Easy fan-out
- Per-job CPU/RAM/GPU choices
- Cleaner sandbox model for arbitrary Claude/Codex tasks
- Persistent shared Volume mounted into every worker
If your current DO setup is one machine with a mounted 1 TB volume, then the annoying part is: scaling means provisioning more droplets, copying/mounting data, managing queues, cleaning workers, etc.
Modal is better when you want:
run 200 Claude Code jobs against the same corpus
then scale back to 0
What I’d do
For 1 TB, I’d try Modal Volume directly before JuiceFS.
V0:
upload/sync DO volume -> Modal Volume
run Modal Sandbox workers
mount volume at /data
/workspace is local writable scratch
output each job to /results/job-id
Use conventions:
/data read-only by policy
/workspace writable local scratch
/results per-job output only
Do not let agents write into /data.
When I would not use Modal Volume
I’d stay with DO / use JuiceFS / use R2 if:
- the 1 TB changes constantly
- you need access from non-Modal machines
- you need true immediate multi-writer visibility
- you want cheapest 24/7 always-on workers
- you need custom FUSE/kernel/network behavior
- your workload is just a few steady workers
My call
For 1 TB, read-heavy, agent workers: use Modal Volume.
It is probably simpler than JuiceFS and much more scalable than a single DigitalOcean volume. The only thing I’d be careful about is not using it as a shared mutable live filesystem. Keep shared data read-only, write outputs per job, and use a DB/queue for coordination.
How powerful are modal workers ? can they run codex cli / claude code ?
Yes. Modal workers are more than powerful enough to run Codex CLI / Claude Code. Those tools are mostly network-bound agent CLIs, not heavy local inference workloads.
How powerful are Modal workers?
By default, a Modal Function/Sandbox starts tiny: 0.125 CPU cores and 128 MiB RAM, but you can request more CPU, memory, disk, and GPUs. Modal’s resource docs say Functions/Sandboxes can request larger CPU/memory similarly to Kubernetes. citeturn913557search0
For Claude/Codex workers, I’d use something like:
cpu=2-8
memory=4096-32768
timeout=3600+
For large repo/corpus work:
cpu=8
memory=32768
ephemeral_disk=50_000 # MB-ish depending Modal API version
No GPU needed unless you are running local models.
Modal also explicitly markets Sandboxes for executing untrusted user/agent code and says you can spin up thousands of isolated secure Sandboxes. citeturn913557search4turn913557search7
Can they run Claude Code?
Yes. Claude Code supports Linux, including Ubuntu/Debian/Alpine, with 4 GB+ RAM and an internet connection. citeturn913557search2
Modal even has a JS example that builds a Sandbox image with bash, curl, git, ripgrep, installs Claude Code via:
curl -fsSL https://claude.ai/install.sh | bash
and then creates a Modal sandbox from that image. citeturn913557search19
So yes, this is a first-class-ish use case.
Can they run Codex CLI?
Almost certainly yes. Treat it like any Node/Rust/native CLI: bake it into the Modal image, inject OPENAI_API_KEY, mount /data, run in /workspace.
Modal Sandboxes can run arbitrary commands via Sandbox.exec, with stdout/stderr/stdin available through the returned process object. citeturn913557search1
Practical architecture
Modal Sandbox
/data Modal Volume, read-only corpus
/workspace local writable disk
/results output path
Env:
ANTHROPIC_API_KEY
OPENAI_API_KEY
The worker command is basically:
cd /workspace
claude -p "$(cat task.md)"
or:
cd /workspace
codex exec "$(cat task.md)"
Important gotchas
Do not run agents from /data.
Use:
/data shared read-only corpus
/workspace writable scratch
Also avoid having many workers write to the same Modal Volume path. Use per-job directories:
/results/job_abc123/
/results/job_def456/
My call
For 1 TB read-heavy data and many agent workers:
Modal Volume for /data
Modal Sandboxes for Claude/Codex
local /workspace for writes
DB/queue for coordination
This is strong enough. Start there before inventing JuiceFS/K8s.
how do secrets work for claude code / codex cli ?
Use Modal Secrets to inject API keys as environment variables into each worker/sandbox. Then Claude Code/Codex pick them up from env.
Claude Code
Claude Code can authenticate from:
ANTHROPIC_API_KEY=...
Anthropic’s docs say ANTHROPIC_API_KEY is sent as the X-Api-Key header, and in non-interactive mode claude -p, the key is used automatically when present. citeturn225146search12
So in a worker:
export ANTHROPIC_API_KEY=sk-ant-...
claude -p "do the task"
Codex CLI
Codex can use env-var auth too. OpenAI’s Codex auth docs say you can set an env_key for provider-specific API keys, and for normal OpenAI usage the key is typically:
OPENAI_API_KEY=...
citeturn225146search10
So:
export OPENAI_API_KEY=sk-proj-...
codex exec "do the task"
In Modal
Create a secret:
modal secret create agent-keys \
ANTHROPIC_API_KEY=sk-ant-... \
OPENAI_API_KEY=sk-proj-...
Modal Secrets are dictionaries of environment variables injected into containers, and Modal says they can be created in the dashboard, from code, or via CLI. citeturn868683search1turn868683search3
Then attach it to the function/sandbox.
For a Modal Function:
import modal
app = modal.App("agent-workers")
image = (
modal.Image.debian_slim()
.apt_install("curl", "git", "ripgrep", "nodejs", "npm")
.run_commands(
"npm install -g @anthropic-ai/claude-code",
"npm install -g @openai/codex"
)
)
@app.function(
image=image,
secrets=[modal.Secret.from_name("agent-keys")],
cpu=4,
memory=8192,
timeout=3600,
)
def run_agent(prompt: str):
import os, subprocess, pathlib
pathlib.Path("/workspace").mkdir(exist_ok=True)
result = subprocess.run(
["claude", "-p", prompt],
cwd="/workspace",
text=True,
capture_output=True,
env=os.environ.copy(),
)
return {
"stdout": result.stdout,
"stderr": result.stderr,
"code": result.returncode,
}
For Codex:
result = subprocess.run(
["codex", "exec", prompt],
cwd="/workspace",
text=True,
capture_output=True,
env=os.environ.copy(),
)
Important security pattern
Do not put API keys in:
/data
/workspace
.env files inside agent-visible repos
task prompts
logs
Use env injection only.
For your setup:
Modal Secret:
ANTHROPIC_API_KEY
OPENAI_API_KEY
R2_READ_ONLY_ACCESS_KEY_ID
R2_READ_ONLY_SECRET_ACCESS_KEY
/data:
mounted read-only corpus
/workspace:
writable scratch, no secrets written
/results:
per-job output
One subtle issue: agents can run shell commands, so they can technically do env and see keys. That is unavoidable if the CLI needs the key directly. If you don’t want the agent process to be able to inspect provider keys, put a tiny LLM proxy in front of Claude/OpenAI and give the worker only a short-lived per-job token with budget/rate limits.
Great can you write up a prompt for my coding agent to make the change from digital ocean to modal ? Codex CLI not claude code ,but otherwise mimick laude that coding agent example you said.
Use this as the Codex prompt:
We need to migrate our current “agent worker over a 1TB dataset” setup from a DigitalOcean volume to Modal.
Current setup:
- Data currently lives on a DigitalOcean volume.
- We want to run many ephemeral Codex CLI workers against that shared dataset.
- The dataset should be treated as read-only by agent workers.
- Each worker should have its own writable scratch directory.
- Outputs should be written to per-job result directories, not back into the shared dataset.
- We are using Codex CLI, not Claude Code.
Goal:
Build a Modal-based worker system that can:
1. Store/mount the dataset using a Modal Volume.
2. Spin up Modal workers/sandboxes/functions that run Codex CLI.
3. Mount the shared dataset at `/data`.
4. Use `/workspace` as local writable scratch.
5. Write final outputs to `/results/<job_id>/`.
6. Inject secrets via Modal Secrets, especially `OPENAI_API_KEY`.
7. Avoid putting secrets in files, prompts, logs, or mounted volumes.
8. Make it easy to submit jobs programmatically.
Please inspect the current repo first and adapt to the existing structure. Do not rewrite everything unnecessarily. Prefer a small, clean integration.
Implementation requirements:
- Add Modal as a dependency if not already present.
- Create a Modal app, probably in something like `modal_workers/agent_worker.py` or an equivalent location that fits this repo.
- Define a Modal image that installs:
- Node.js / npm if needed
- Codex CLI
- git
- ripgrep
- fd/findutils if useful
- jq
- Python utilities already needed by the repo
- Define or reference a Modal Volume for the dataset, e.g. `agent-dataset`.
- Define or reference a Modal Volume for results if useful, e.g. `agent-results`.
- Mount dataset Volume at `/data`.
- Mount results Volume at `/results`.
- Ensure workers run from `/workspace`, not `/data`.
- Ensure the prompt tells Codex that `/data` is read-only and `/workspace` is writable.
- Use `OPENAI_API_KEY` from a Modal Secret, e.g. `agent-keys`.
The core worker should look conceptually like:
```python
@app.function(
image=image,
volumes={
"/data": dataset_volume,
"/results": results_volume,
},
secrets=[modal.Secret.from_name("agent-keys")],
cpu=4,
memory=8192,
timeout=3600,
)
def run_codex_job(job_id: str, prompt: str, extra_context: dict | None = None):
...
Inside the worker:
- Create
/workspace/<job_id>or similar. - Write the task prompt to
/workspace/<job_id>/task.md. - Run Codex CLI from the workspace directory.
- Use non-interactive Codex mode, likely
codex exec. - Include clear instructions in the Codex prompt:
The dataset is mounted at /data and must be treated as read-only.
Do not modify files in /data.
Use /workspace for scratch files, scripts, intermediate notes, and temporary indexes.
Write final outputs to /results/<job_id>/.
Do not print environment variables or secrets.
- Capture stdout, stderr, return code, and runtime.
- Save stdout/stderr to
/results/<job_id>/logs/. - Save a final response or summary to
/results/<job_id>/answer.mdif Codex produces one. - Return a structured Python dict with:
- job_id
- status
- return_code
- stdout_tail
- stderr_tail
- result_path
Also add a local submit/test script, for example:
python -m modal_workers.submit_job --job-id test-001 --prompt "Inspect /data and summarize what datasets are available."
or a simple Python function that calls the Modal function remotely.
Add a data migration script or documented command path for copying data from the current DigitalOcean volume into the Modal Volume. Use Modal’s documented volume upload approach if available. If direct upload of 1TB is awkward, write a clear staged migration plan:
- Package/sync dataset from DO.
- Upload into Modal Volume.
- Verify file count / byte size / sample reads.
- Run a smoke-test Codex job.
- Freeze DO volume as backup until Modal results are verified.
Important constraints:
- Do not use the Modal Volume as a live multi-writer coordination layer.
- Do not let workers write into
/data. - Per-job results must go under unique paths like
/results/<job_id>/. - Use an external DB/queue if the repo already has one; otherwise keep this PR focused on Modal worker execution, not a full queue system.
- Keep current DigitalOcean path working until the Modal path is proven. Ideally add a config flag like
WORKER_BACKEND=modal|digitaloceanor similar. - Make the migration incremental.
Please produce:
- The Modal worker implementation.
- The submit/test script.
- Any required dependency/config changes.
- A README section explaining:
- how to create the Modal secrets
- how to create/upload the Modal Volume
- how to run a smoke test
- how to submit a real job
- known limitations around Volume concurrency and write visibility
- A minimal smoke test that verifies:
/datais mounted/workspaceis writableOPENAI_API_KEYis present but never printed- Codex CLI can run
- results are written to
/results/<job_id>/
Before coding, inspect the repo and tell me the smallest clean set of files you plan to touch. Then implement it.
Optional stricter add-on:
```text
Style preferences:
- Keep code minimal and boring.
- Prefer typed Python.
- Avoid clever abstractions.
- Do not introduce a huge framework.
- Do not log secrets.
- Do not let Codex run from the mounted dataset directory.
- Use per-job directories everywhere.
could i use modal to create a worker that creates a jupyter notebook and publishes it ? although i guess you wouldnt see the research as it happens.
I’ll separate “generate a notebook artifact” from “watch the research live,” because Modal can do the first cleanly and the second needs streaming/log/UI plumbing.
Yes. Modal is a good fit for this.
You have two different products/patterns:
1. Batch-generated notebook report
This is what I’d do first.
Modal worker
reads /data
runs Codex / scripts
creates report.ipynb
executes notebook
exports report.html
uploads/publishes HTML
Modal supports Sandboxes for executing agent/user code, and Volumes/CloudBucketMounts can be attached so data is available inside the sandbox. citeturn501121search7turn501121search4
The worker can use:
jupyter nbconvert --execute --to notebook report.ipynb
jupyter nbconvert --to html report.ipynb
nbconvert supports executing notebooks and saving outputs back into .ipynb, and also converting notebooks to formats like HTML from the command line. citeturn501121search6turn501121search1
Then publish to one of:
R2 public bucket
Cloudflare Pages
S3 static hosting
your own app route
Modal web endpoint
This gives you a polished artifact:
/reports/job-123/report.ipynb
/reports/job-123/report.html
/reports/job-123/artifacts/*.png
/reports/job-123/logs.txt
2. Live Jupyter notebook while research happens
Also possible, but different.
Modal has hosted notebooks and also an example of running a Jupyter notebook server inside a Modal Sandbox, then exposing it via a URL. citeturn501121search0turn501121search2 Modal also supports tunnels to expose live TCP ports from a container/Sandbox. citeturn501121search12
That would let you open a running Jupyter server and watch/edit interactively.
But for agent research, live Jupyter is often the wrong primitive. Better:
Live:
stream logs/events/tool calls to your UI
Final:
publish notebook + HTML report
The agent can still write the notebook incrementally, but you probably don’t want to watch cells appear live unless you’re debugging.
The architecture I’d use
submit_research_job(prompt, dataset_refs)
↓
Modal Sandbox / Function
↓
/data mounted read-only corpus
/workspace writable scratch
/results report output
↓
Codex CLI does research
↓
Python script builds report.ipynb
↓
nbconvert executes + exports HTML
↓
Upload report.html to R2/Pages
↓
Return public URL
The notebook is the audit artifact, not necessarily the live UI.
Codex flow
Prompt Codex like:
You are running inside a Modal worker.
Dataset:
- /data is read-only.
- Use it only for reading corpus files.
Workspace:
- /workspace is writable.
- Write scripts, notes, charts, and intermediate files here.
Final report:
- Create /results/{job_id}/report.ipynb.
- The notebook should include:
1. Research question
2. Dataset/files inspected
3. Methods
4. Code cells for reproducibility
5. Findings
6. Charts/tables if useful
7. Limitations
- Also create /results/{job_id}/summary.md.
- Do not print secrets or environment variables.
Then after Codex exits, your wrapper runs:
jupyter nbconvert \
--execute \
--to notebook \
--inplace /results/$JOB_ID/report.ipynb
jupyter nbconvert \
--to html \
--output /results/$JOB_ID/report.html \
/results/$JOB_ID/report.ipynb
The live-research problem
You’re right: you would not automatically “see the research as it happens” if it’s just a background Modal job.
Fix that with one of these:
Simple V0
stream stdout/stderr from Codex
save logs to /results/job-id/logs.txt
poll job status from your app
Better
Codex wrapper emits events:
- started
- command_run
- file_read
- note_written
- chart_created
- notebook_cell_added
- final_report_ready
Best
Use your own web UI:
left: live event stream
right: generated notebook/report preview
bottom: artifacts/files
I would not start by trying to make Jupyter itself the live UI. Use logs/events for live visibility, notebook for final proof.
Bottom line
Yes:
Modal worker creates notebook: good
Modal worker publishes HTML: good
Modal hosted/running Jupyter: possible
Live research visibility: build log/event streaming, not just notebook publishing
For your product, the killer output is probably “research report with reproducible notebook attached”: HTML for normal users, .ipynb for technical users, logs/artifacts for auditability.