Storage Layer
DjinnBot uses a distributed storage stack to share files across all containers — the engine, API server, and every dynamically spawned agent container see the same filesystem at /data. This is built on two components: RustFS for S3-compatible object storage and JuiceFS for presenting that storage as a standard POSIX directory tree.
Architecture
graph LR
Engine["Engine"] --> FUSE["JuiceFS FUSE Mount<br/>/data"]
Agent1["Agent Container"] --> FUSE
Agent2["Agent Container"] --> FUSE
API["API Server"] --> FUSE
FUSE --> Redis2["Redis DB 2<br/>(Metadata)"]
FUSE --> RustFS["RustFS<br/>(S3 Object Store)"]
RustFS --> Disk["rustfs-data volume"]
style FUSE fill:#14b8a6,color:#fff
style RustFS fill:#14b8a6,color:#fff
style Redis2 fill:#dc2626,color:#fff
style Disk fill:#6b7280,color:#fff
style Engine fill:#8b5cf6,color:#fff
style Agent1 fill:#f59e0b,color:#000
style Agent2 fill:#f59e0b,color:#000
style API fill:#8b5cf6,color:#fff
RustFS
RustFS is an S3-compatible object storage server written in Rust. It holds the actual file data.
- Runs as
djinnbot-rustfswith a persistent Docker volume (rustfs-data) - Exposes S3 API on port 9000 (internal network only)
- Exposes an admin console on port 9001 (exposed to host for debugging)
- Authenticated with
RUSTFS_ACCESS_KEYandRUSTFS_SECRET_KEY
JuiceFS
JuiceFS is a POSIX-compliant distributed filesystem that sits on top of any object store. It runs as a FUSE mount inside a privileged container.
- Runs as
djinnbot-juicefsusing thejuicedata/mount:ce-v1.3.1image - Metadata engine: Redis DB 2 (separate from the event bus on DB 0)
- Data backend: RustFS at
http://rustfs:9000/djinnbot - Mount point:
/jfsinside the container, exposed via thejuicefs-dataDocker named volume
On startup, the container runs two commands:
juicefs format(idempotent) — creates the filesystem metadata if it doesn’t existjuicefs mount— mounts the FUSE filesystem with production-tuned options
Mount options
--cache-dir /var/jfsCache # Local SSD cache for read performance
--cache-size 20480 # 20 GB cache (configurable via JUICEFS_CACHE_SIZE)
--buffer-size 300 # 300 MB write buffer
--prefetch 1 # Prefetch 1 block ahead
--max-uploads 20 # 20 concurrent uploads to RustFS
--attr-cache 1 # 1 second attribute cache
--entry-cache 1 # 1 second directory entry cache
--dir-entry-cache 1 # 1 second directory listing cache
--backup-meta 3600 # Backup metadata every hour
--trash-days 7 # Soft-delete retentionWhat Lives on JuiceFS
Everything under /data is on JuiceFS:
/data/
├── workspaces/ ← project repos (cloned from GitHub)
│ ├── {projectId}/ ← bare git repo per project
│ └── .runs/ ← ephemeral run worktrees
│ └── {runId}/ ← git worktree for a pipeline run
├── sandboxes/ ← persistent agent home directories
│ └── {agentId}/ ← symlinked to /home/agent in containers
│ ├── clawvault/ ← memory vaults
│ │ ├── {agentId}/ ← personal vault
│ │ └── shared/ ← team shared vault
│ └── task-workspaces/ ← persistent task worktrees (pulse)
│ └── {taskId}/ ← git worktree for a pulse task
└── uploads/ ← file attachmentsHow Containers Access It
The juicefs-data Docker named volume is mounted at /data in the engine, API server, and Slack bridge containers. When the engine spawns agent containers dynamically via the Docker API, it mounts the same volume.
Agent containers have their sandbox mounted via JuiceFS --subdir at /home/agent, which maps to /data/sandboxes/{agentId}/. This gives each agent a persistent home directory that survives container destruction.
Health Monitoring
The API server health endpoint (/v1/health) checks both storage components:
{
"storage": {
"juicefs_mounted": true,
"rustfs_healthy": true,
"juicefs_volume": "djinnbot"
}
}The CLI djinn status command also reports JuiceFS and RustFS health.
Durability
- File data is stored in the
rustfs-dataDocker volume and persists across container restarts and rebuilds - Filesystem metadata is stored in Redis DB 2 and backed up hourly by JuiceFS (
--backup-meta 3600) - Soft deletes are retained for 7 days (
--trash-days 7) - Storage volumes are explicitly preserved during
djinn update— only application containers are rebuilt
Configuration
Storage credentials are set via environment variables (see .env.example):
RUSTFS_ACCESS_KEY=djinnbot-rustfs-admin
RUSTFS_SECRET_KEY=djinnbot-rustfs-secret
JUICEFS_VOLUME_NAME=djinnbot
JUICEFS_CACHE_SIZE=20480 # MB