Skip to main content
Filesystems give your app durable mounted storage inside the sandboxed runtime. Each app user runs in an isolated runtime, and your code can work with normal container-local paths plus any filesystems declared on the app. Use mounted filesystems for uploaded documents, generated reports, exports, cached model artifacts, and large intermediate outputs that should outlive one request.

Create a filesystem

capsule fs create reports

Mount it

app = cpsl.App(
    name="reports",
    image=cpsl.Image(),
    filesystems={"/data": cpsl.FileSystem("reports")},
)
Inside the runtime, /data behaves like a normal directory. Your handlers, tasks, schedules, and workflows can all read and write through that path. The mount is an app/workspace resource, not a private directory automatically created per end user. If files are user-specific, namespace paths yourself, for example /data/users/{session.user.id}/summary.md, or keep private/queryable state in user-scoped collections and store only generated artifacts in the filesystem.

Write files

@app.task()
async def write_report():
    with open("/data/summary.txt", "w") as f:
        f.write("Generated by Capsule\n")

Accept uploads

@app.message()
async def handle(session: cpsl.Session, msg: cpsl.Message):
    upload = await session.prompt_file(
        message="Upload a CSV",
        accept=".csv,text/csv",
        path="/data/uploads",
    )
    await session.reply(f"Uploaded {upload.name} to {upload.path}")

Manage files with the CLI

capsule fs ls reports /
capsule fs upload reports ./report.csv /incoming/report.csv
capsule fs download reports /incoming/report.csv ./report.csv

Filesystems versus collections

Use collections for queryable records. Use filesystems for files and artifacts. Many apps use both: a collection row stores metadata and points at a file in a mounted filesystem.