Skip to content
Console →
Website →
Asking an AI? Paste this URL https://keelson.dev/llms.txt

Storage and Data

A Keelson app runs with an ephemeral local filesystem.

AreaPersistenceIntended use
App directory (source code and dependencies)Ephemeral and replaced by deploymentsApplication code and installed packages
/tmpEphemeral and lost with the instanceTemporary scratch space

Keelson does not mount or create /data, and an application cannot normally create directories at the filesystem root. Use /tmp for bounded temporary work. Like other writes to the container filesystem, those bytes consume instance memory and disappear with the instance. Store durable relational data in Keelson Managed SQLite (db.mode: libsql). For durable files, use an appropriate Keelson SDK: the Files SDK for private files or the Media SDK for content served to app members.


Choose the durable interface that matches how the data is addressed and served.

  • Application records, such as customers, projects, and events: Managed SQLite (db.mode: libsql)
  • Private files, such as state, settings, and generated reports that only the app reads: Files SDK
  • Uploads and media served over HTTP: Media SDK
  • Recreatable caches and temporary files: /tmp; it is ephemeral and consumes instance memory

Do not use the local filesystem for persistence

Section titled “Do not use the local filesystem for persistence”

Do not put anything on the local filesystem that you cannot afford to lose. /data is not a platform-provided writable path. Choose storage by purpose:

  • Business data and other durable records: Keelson Managed SQLite (db.mode: libsql)
  • Private key-addressed files: use the Files SDK and retain each key
  • Published uploads and media: use the Media SDK and retain each returned ID
  • Temporary files: use /tmp, then delete scratch files promptly
  • Recreatable caches: dependencies and build output do not require durable storage
  • Application logs: inspect them through Keelson’s logging features instead of accumulating log files locally

For new apps, we recommend Keelson Managed SQLite. The platform provisions a dedicated managed SQLite-compatible libSQL database for each app and isolates it at the database level by workspace and app.

  • No separate setup: connection details are injected as KEELSON_DB_URL and KEELSON_DB_AUTH_TOKEN
  • Workspace isolation: each app’s credentials are scoped to its own database
  • Durable commits: writes go to the primary and are durable when committed
  • Regional placement: the primary is located in Tokyo
  • Point-in-time recovery: available on every plan; the recovery window depends on the plan

JavaScript, TypeScript, and Python applications can connect with a libSQL client. Enable the managed database explicitly in keelson.yaml; the platform does not infer this choice from application code.

db:
mode: libsql

Use an external database with db.mode: none

Section titled “Use an external database with db.mode: none”

If you need PostgreSQL, MySQL, or an externally managed libSQL database, set db.mode: none. Add its connection details as secrets yourself. Keelson does not provision or inject credentials for an external database.

db:
mode: none

Frameworks that normally default to file-based SQLite need a supported durable database integration. For Django, use this route with an external database, such as PostgreSQL. Keelson does not currently provide a supported Django backend for Managed SQLite. There is no durable deployment path for a local SQLite file.

A SQLite database placed directly on the container filesystem is not persistent. /data is neither mounted nor normally writable, and any writable local path can be lost on restart or scale-to-zero. Deployment checks fail closed when they detect an undeclared file-SQLite configuration.

Move applications using file-based clients such as better-sqlite3 or sqlite3 to a libSQL client and use db.mode: libsql. A regenerable temporary SQLite database may be declared under db.local_sqlite, but its path must be under /tmp or use :memory: and it remains intentionally ephemeral.

db:
mode: none
local_sqlite:
policy: ephemeral
paths:
- /tmp/cache.db
reason: "Rebuilt from the upstream API after every cold start"

Use the Files SDK for private durable files that only the app can read. Install it from the public package registry with npm install @keelsonhq/files, pip install keelson-sdk (from keelson import files), or go get github.com/keelsonhq/go-sdk (.../go-sdk/files).

Address each object by its Files SDK key and keep that key, ownership, and other application metadata in Managed SQLite. Files SDK objects have no URL and are not served over HTTP. See Files and media for operations, limits, and local-development behavior.

Use the Media SDK for images, PDFs, attachments, and other durable content served to app members. Install it from the public package registry with npm install @keelsonhq/media, pip install keelson-sdk (from keelson import media), or go get github.com/keelsonhq/go-sdk (.../go-sdk/media).

Uploading returns an object ID. Keep that ID, ownership, and other application metadata alongside the related record in Managed SQLite. Media URLs use the app’s authentication and are available to app members; they are not public URLs. See Files and media for operations, limits, and local-development behavior.

Managed SQLite commits are stored by the managed database service rather than on the app’s ephemeral filesystem. Restarting or redeploying the app therefore does not discard committed database data.

Daily backups, manual snapshots, and point-in-time recovery (PITR) are available on every plan. Manual snapshots are limited to five per app per day.

PlanDaily backup retentionPITR window
Starter1 generation24 hours
Plus3 generations7 days
Team7 generations14 days

Saved backups and any eligible PITR restore point can be downloaded on every plan. A point-in-time export creates a temporary database fork, produces a dump, and removes the fork after download. Export availability is not plan-gated.

PITR recovers a database to an eligible time within the plan window. Restore switches to a fork and retains the pre-restore generation for 72 hours, during which the restore can be undone.

ResourceCovered by Managed SQLite recovery?
Committed Managed SQLite (libSQL) dataYes, through daily backups, manual snapshots, and PITR within the plan window
Files under /tmpNo; /tmp is ephemeral
Objects in an external storeNo; use that provider’s recovery features
Application source codeNo; it is uploaded on each deployment
Environment variablesNo; they are managed separately in the console

Keep application code in version control such as Git. Put durable application records in Managed SQLite so they are eligible for the managed recovery path. Export critical data separately when your retention or recovery requirements extend beyond the PITR window.

Choose a recovery time within the plan’s window when starting a point-in-time recovery. Recovery operates on one app’s database and does not expose data from other apps. The requested time must also fall within the recovery window made available by the managed database service.

When to use an external database or object store

Section titled “When to use an external database or object store”

Managed SQLite works well for many internal applications, but an external service may be a better fit in these cases.

SituationWhyPossible choice
The dataset’s capacity or performance requirements outgrow Managed SQLiteThe workload needs a different capacity or performance profilePostgreSQL
The workload has very high concurrent write volumeSQLite serializes writersPostgreSQL or MySQL
BI or core business systems must query the data directlyOther systems need independent accessShared PostgreSQL or a data warehouse
Operations require specialized replication or recovery controlsThe database needs capabilities outside the managed defaultsManaged PostgreSQL

For a customer, inventory, or project management app, store records in Managed SQLite.

db:
mode: libsql

For CSV imports or scheduled aggregation, use /tmp as intermediate workspace and save records in Managed SQLite. Write a downloadable report with the Files SDK and keep its key in Managed SQLite.

For an internal RAG or chat backend, store document metadata and embeddings in Managed SQLite. Store private source documents with the Files SDK and keep their keys in Managed SQLite.

Files written to the local container filesystem can be lost with the instance. /tmp is the supported writable scratch location; /data is not a writable platform mount. Put durable records in Managed SQLite and durable file objects in the Files SDK or Media SDK, according to who should be able to read them.

# Wrong: local paths are ephemeral, and /data is not a writable mount.
db_path = "./data.db"
db_path = "/tmp/data.db"
db_path = "/data/main.db"
# Correct: connect to Managed SQLite with the injected credentials.
import os
import libsql
conn = libsql.connect(
database=os.environ["KEELSON_DB_URL"],
auth_token=os.environ["KEELSON_DB_AUTH_TOKEN"],
)

An app that tries to create a database under /data with better-sqlite3, sqlite3, or a similar file client cannot rely on that path being writable or durable and fails deployment checks. Migrate to a libSQL client and Managed SQLite.

Daily backups, manual snapshots, PITR, and exports cover committed Managed SQLite data, not local files. Check the daily-backup retention and PITR window for your plan. Download a restore point when you need an independently retained copy.

Letting temporary storage grow without bounds

Section titled “Letting temporary storage grow without bounds”

Remove scratch files when processing finishes. Writable container files consume instance memory. Unbounded writes can exhaust that memory and crash the instance. Keep temporary work in /tmp; Keelson does not assign /data a separate disk capacity.

Code such as sqlite3.connect("/data/main.db") assumes durable local storage that Keelson does not provide. Read the injected Managed SQLite credentials and use a libSQL client instead.

Python: write to Managed SQLite with libSQL

Section titled “Python: write to Managed SQLite with libSQL”
import os
import libsql
def get_db():
conn = libsql.connect(
database=os.environ["KEELSON_DB_URL"],
auth_token=os.environ["KEELSON_DB_AUTH_TOKEN"],
)
conn.execute("""
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
return conn
conn = get_db()
conn.execute("INSERT INTO items (name) VALUES (?)", ("Example item",))
conn.commit()

The corresponding keelson.yaml enables Managed SQLite:

slug: my-app
runtime: python-slim
command: "python app.py"
db:
mode: libsql

Declare the libSQL client in requirements.txt or pyproject.toml. Keelson installs dependencies during the image build. The startup command must only start the app, and the app must listen on the PORT value Keelson injects.

Node.js: write to Managed SQLite with libSQL

Section titled “Node.js: write to Managed SQLite with libSQL”
import { createClient } from "@libsql/client";
const db = createClient({
url: process.env.KEELSON_DB_URL,
authToken: process.env.KEELSON_DB_AUTH_TOKEN,
});
await db.execute(`
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
await db.execute({
sql: "INSERT INTO items (name) VALUES (?)",
args: ["Example item"],
});
  • Business data for internal apps, such as customers, projects, and stock
  • AI-generated apps that need a database immediately, without provisioning an external database
  • Many small apps, each with database-level app and workspace isolation
  • Very large datasets where an external database offers a better capacity and performance profile
  • Concurrent access from several independent systems, rather than access primarily from the deployed app
  • Specialized recovery or replication requirements beyond the plan’s managed capabilities
  • Direct integration with BI or core business systems that need their own database connection

Managed SQLite: No fixed limit by plan. The underlying platform’s technical limits still apply. If we detect load far beyond normal business use, or use that affects platform stability, other customers, or our reasonable operating costs, we may restrict or suspend the application. In an emergency, we may impose restrictions without prior notice.

The separate plan storage quota, from 10 GB on Starter to 50 GB on Team, is a workspace-wide aggregate. It counts deployment artifacts, stored file data, snapshot dumps and backups, and metered app data usage. It does not count the Managed SQLite database itself; Managed SQLite usage is metered separately. Purchasing additional workspace storage does not change the database’s separate technical limits. The container filesystem does not provide durable capacity.

Local container data can be lost. Managed SQLite data remains available after a redeploy because committed data is stored outside the app’s ephemeral filesystem. Eligible recovery times are limited by the plan’s PITR window.

Yes. Managed SQLite is the default recommendation, but an app can connect to an external PostgreSQL, MySQL, or other database over an outbound connection. Set db.mode: none and supply its credentials as secrets.

Local filesystems and Files SDK objects are isolated. If another app needs private data, expose an authenticated API from the app that owns the Files SDK object. Use the Media SDK only for content intended to be served to app members; its URL is not public and uses the app’s authentication.

How long does point-in-time recovery take?

Section titled “How long does point-in-time recovery take?”

Recovery time depends on data volume and the managed database service. Do not assume a fixed recovery time when defining an application’s recovery objective.

Use Keelson Managed SQLite (libSQL) and declare db.mode: libsql.

Read KEELSON_DB_URL and KEELSON_DB_AUTH_TOKEN from the environment and use a libSQL client. Do not use file-based SQLite.

Store private durable files with the Files SDK and keep each key in Managed SQLite. Use the Media SDK for content served to app members.

Use /tmp for bounded scratch data. Do not write to /data; Keelson does not provide it as a writable platform mount.