docs/deployment.md
Deployment
Run locally, use remote S3, deploy services, and publish releases.
Four deployment scenarios, from simplest to most complex.
1. Single Developer, Local
Everything runs on your machine. SQLite for metadata, MinIO (Docker) for file storage.
Prerequisites
Setup
# Install agent-fsbun add -g @desplega.ai/agent-fs
# Initialize (starts MinIO container, creates DB, registers local user)agent-fs init --local
# Verifyagent-fs config showagent-fs write /hello.md --content "Hello from agent-fs"agent-fs cat /hello.mdThis creates ~/.agent-fs/ with:
agent-fs.db— SQLite database (metadata, FTS5 index, embeddings)config.json— S3 endpoint, credentials, embedding settingsagent-fs.pid/agent-fs.log— daemon PID and logs (when running as daemon)
Running as a daemon
agent-fs daemon start # Start background daemonagent-fs daemon status # Check if runningagent-fs daemon stop # Stop daemonThe daemon serves both the HTTP REST API and the MCP endpoint on 127.0.0.1:7433. The CLI and agent-fs mcp proxy both require a running daemon.
2. Single Developer, Remote S3
Use Cloudflare R2, AWS S3, or any S3-compatible storage instead of local MinIO.
Setup
agent-fs init --local
# Then configure remote S3agent-fs config set s3.endpoint "https://<account-id>.r2.cloudflarestorage.com"agent-fs config set s3.bucket "agent-fs"agent-fs config set s3.accessKeyId "<your-access-key>"agent-fs config set s3.secretAccessKey "<your-secret-key>"agent-fs config set s3.region "auto"S3 Provider Notes
| Provider | endpoint | region | forcePathStyle |
|---|---|---|---|
| MinIO (local) | http://localhost:9000 | us-east-1 | true |
| Cloudflare R2 | https://<account>.r2.cloudflarestorage.com | auto | true |
| AWS S3 | https://s3.<region>.amazonaws.com | your region | false |
| DigitalOcean Spaces | https://<region>.digitaloceanspaces.com | your region | false |
S3 Versioning
Enable S3 versioning on your bucket for full diff and revert support. Without versioning, these operations degrade (no content-level diffs, revert creates from latest only).
# AWSaws s3api put-bucket-versioning --bucket agent-fs --versioning-configuration Status=Enabled
# MinIOmc version enable myminio/agent-fs3. Team, Shared Server
Deploy the HTTP server so multiple developers or agents can share the same filesystem.
Setup
# On the serveragent-fs init --localagent-fs server --host 0.0.0.0 --port 7433Important: The default bind address is
127.0.0.1(localhost only). Use--host 0.0.0.0to accept external connections.
Register users
# Each team member gets their own identitycurl -X POST http://your-server:7433/auth/register \ -H "Content-Type: application/json" \ -d '{"email": "alice@example.com"}'# Returns: { "apiKey": "..." }Client configuration
Each team member configures their CLI or MCP client:
agent-fs config set api.url "http://your-server:7433"agent-fs config set api.key "<their-api-key>"Or via environment variables:
export AGENT_FS_API_URL="http://your-server:7433"export AGENT_FS_API_KEY="<their-api-key>"RBAC
Users have roles per-organization and per-drive:
| Role | Permissions |
|---|---|
viewer | Read files, search, list |
editor | Read + write, edit, delete files |
admin | Full access + manage users, drives, orgs |
Key rules:
- Drive membership is explicit. A drive is only visible and usable for users with a drive membership row. New drives grant the creator admin membership automatically; invite other users per drive (or rely on org-invite's default-drive grant).
- Member management is admin-only. Inviting, listing, updating, and removing org members requires org
admin. Managing drive members requires driveadminor admin of the owning org. Creating drives in an org requires orgadmin. - Write paths all enforce editor-or-better — the JSON ops route, the binary
PUT /rawroute, and FUSE mounts share the same check. Viewers can read everywhere they're a member but cannot write through any surface. - Org/drive IDs are bound. A request that addresses a drive under the wrong org — or any org/drive the caller has no membership in — returns
404, indistinguishable from a nonexistent ID.
4. Multi-Agent, Hosted
Deploy agent-fs as shared infrastructure for autonomous agents.
Architecture
Agent A (Claude Code) ──┐
Agent B (Cursor) ───┤──→ agent-fs server ──→ SQLite + S3
Agent C (custom) ───┘ :7433Setup
- Deploy server with remote S3 (see scenario 2 for S3 config)
- Register each agent as a user with its own API key
- Create shared drives and assign access via RBAC
- Configure each agent's MCP client with its API key
# Register agentscurl -X POST http://agent-fs:7433/auth/register -d '{"email": "agent-a@agents.local"}'curl -X POST http://agent-fs:7433/auth/register -d '{"email": "agent-b@agents.local"}'Each agent gets its own identity, so file operations are attributed to the agent that performed them. Use log to see who wrote what.
Multi-tenant isolation model
When mutually distrustful users or agents share one server, understand what the boundary is — and is not:
- Isolation is enforced at the application layer by RBAC: every HTTP, MCP, raw, and FUSE operation proves the caller has an explicit role on the target org/drive before touching data. Cross-tenant org/drive/comment IDs resolve to
404, so tenants can't probe each other's resources. - Storage is a single shared S3 bucket, namespaced by
<orgId>/drives/<driveId>/...key prefixes. There is no per-tenant bucket, credential, or encryption key — anyone holding the *server's* S3 credentials (or the server's SQLite DB) can read all tenants' data. Tenant isolation holds only as long as the server host and its credentials are trusted. - Signed URLs are an intentional escape hatch. Generation is RBAC-checked (viewer-or-better on the drive), but the resulting presigned S3 URL is an unauthenticated bearer secret until it expires. A tenant who shares a signed URL is sharing read access to that file with anyone who has the URL.
If you need isolation that survives a server-credential leak, run separate agent-fs instances (or buckets) per tenant.
Embedding Providers
Semantic search requires an embedding provider. Configure via environment variable or config.json.
| Provider | Env Variable | Cost | Notes |
|---|---|---|---|
| OpenAI | OPENAI_API_KEY | ~$0.02/1M tokens | Best quality, requires API key |
| Google Gemini | GEMINI_API_KEY | Free tier available | Good quality, generous free tier |
| Local (llama.cpp) | — | Free | Requires local model download, slower |
Priority: environment variable > config.json > none (semantic search disabled).
Configuring in config.json
{ "embedding": { "provider": "openai", "model": "text-embedding-3-small", "apiKey": "sk-..." }}Configuration Reference
The config file lives at ~/.agent-fs/config.json (or $AGENT_FS_HOME/config.json).
{ "s3": { "endpoint": "http://localhost:9000", "bucket": "agent-fs", "region": "us-east-1", "accessKeyId": "minioadmin", "secretAccessKey": "minioadmin", "forcePathStyle": true }, "embedding": { "provider": "openai", "model": "text-embedding-3-small", "apiKey": "sk-..." }, "server": { "host": "127.0.0.1", "port": 7433 }}Troubleshooting
"SQLITEERROR: no such module: fts5"
On macOS, Apple's bundled SQLite doesn't support extensions. Install via Homebrew:
brew install sqliteBun will use the Homebrew version automatically.
MinIO container won't start
Check if port 9000 is already in use:
lsof -i :9000The MinIO container is named agent-fs-minio. Check its status:
docker ps -a --filter name=agent-fs-miniodocker logs agent-fs-minioDaemon won't start
Check for stale PID file:
cat ~/.agent-fs/agent-fs.pidkill -0 $(cat ~/.agent-fs/agent-fs.pid) 2>/dev/null && echo "running" || echo "stale"If stale, remove the PID file and restart:
rm ~/.agent-fs/agent-fs.pidagent-fs daemon start