Database (Managed SQLite)
Use Managed SQLite to store customer records, projects, requests, and other data your app creates, searches, and updates. Keelson provisions a dedicated database for each app, and saved data survives restarts and redeployments. You can also connect to an external database you already use.
What to store where
Section titled “What to store where”Images and configuration files also have storage options that survive restarts. Choose based on what you are storing and how you use it.
| Data and use case | Storage |
|---|---|
| Create, search, and update customer records, projects, or requests | Managed SQLite |
| Read and write configuration files or records of processed data inside the app | Files SDK |
| Display images or let users open PDFs and attachments | Media SDK |
For example, a project tracking app can keep project names and assignees in the database and attached PDFs in Media. Store each attachment’s ID in the database to link the file to its project.
See Files and media for file storage instructions.
Local files are temporary
Section titled “Local files are temporary”Here, “local files” means files written inside the server running your app on Keelson. They are lost when the app restarts, is redeployed, or stops after a period without traffic. This is called “ephemeral” storage.
Changing the folder to ./data.db or /tmp/data.db does not preserve the data across restarts (/data does not exist: Keelson does not create it and the app cannot create it). Scheduled jobs run in a separate environment from the web app and cannot share these files either.
You can use /tmp for files needed only during processing, such as incoming uploads or intermediate conversions. Save anything you need later in one of the persistent stores above.
Apps using SQLite files
Section titled “Apps using SQLite files”Managed SQLite is SQLite-compatible, so the SQL and table definitions of an app built on SQLite usually stay as they are. What changes is the connection: replace the places where sqlite3 or better-sqlite3 write directly to a file inside the server with a libSQL client connection. The client APIs differ: Node.js moves from the synchronous better-sqlite3 to the asynchronous @libsql/client, so add await in each handler. Python’s libsql differs from sqlite3 in row_factory, type adapters, and exception classes. In Python / Node.js, pass the same client a file such as file:local.db and it works as file-based SQLite in local development.
If your app uses sqlite3 or better-sqlite3 to save data that must persist, you can ask your AI agent:
Make this app’s data survive restarts and redeployments on Keelson. Find any file-based SQLite usage, and explain the changes needed for Managed SQLite and how to move existing data.
Deployment checks reject detected SQLite file usage unless it is declared. For regenerable caches only, you can declare temporary SQLite use with db.local_sqlite. This does not make the files survive restarts. See the reference.
Use Managed SQLite
Section titled “Use Managed SQLite”Add this setting to keelson.yaml to provision a dedicated database for the app. Your AI agent normally writes both the configuration and connection code.
db: mode: libsql- No database server setup is needed. Keelson supplies the connection URL and authentication token through the app’s environment variables.
- Each app’s data is separate. Its credentials can access only its own database.
- Committed data survives restarts. Data is saved when the SQL commit completes.
- The database is hosted in the same region as the app. Japan is currently the only available region (
region). US West is in preparation. - You can restore from backups. Daily backups, manual backups, and point-in-time recovery are supported.
Connection code
Section titled “Connection code”In addition to changing the configuration, your app must connect through a libSQL client. SQL works as in SQLite. On Keelson the connection uses the URL and authentication token supplied by Keelson instead of a local file path. In Python / Node.js, fall back to file:local.db when the environment variable is absent, and the same code runs as file-based SQLite in local development (the Go libSQL client cannot open file:, so this pattern does not apply to Go). Compatible clients are available for Python, Node.js, and Go.
These examples create a table and save one record. Add libsql to your Python dependencies or @libsql/client to your Node.js dependencies.
Python
Section titled “Python”import os
import libsql
conn = libsql.connect( database=os.environ.get("KEELSON_DB_URL", "file:local.db"), # file: = local development auth_token=os.environ.get("KEELSON_DB_AUTH_TOKEN", ""),)conn.execute(""" CREATE TABLE IF NOT EXISTS items ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, created_at TEXT DEFAULT CURRENT_TIMESTAMP )""")conn.execute("INSERT INTO items (name) VALUES (?)", ("Sample",))conn.commit()Node.js
Section titled “Node.js”import { createClient } from "@libsql/client";
const db = createClient({ url: process.env.KEELSON_DB_URL ?? "file:local.db", // file: = local development 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 TEXT DEFAULT CURRENT_TIMESTAMP )`);await db.execute({ sql: "INSERT INTO items (name) VALUES (?)", args: ["Sample"] });Add or change tables and columns
Section titled “Add or change tables and columns”A database’s “schema” defines its structure: table and column names, data types, and constraints. If a table does not exist yet, create it with CREATE TABLE IF NOT EXISTS, as in the examples above.
To change tables or columns during deployment, set a command in db.migrate. This process is called a “migration.”
db: mode: libsql migrate: "python migrate.py"db.migrate runs before switching to the new code. If it fails, the app continues serving with the previous code. Any database changes already made are not automatically undone.
Make the command safe to repeat without duplicating tables or data (idempotent). The updated database must remain readable and writable by the previous code. Static sites without command do not run db.migrate. See Update and restore.
Concurrent writes
Section titled “Concurrent writes”SQLite processes writes one at a time. Simultaneous writes from a web app and scheduled jobs, for example, may have to wait. If concentrated writes affect response times, review the workload or consider an external database such as PostgreSQL.
Use an external database
Section titled “Use an external database”To connect to existing PostgreSQL, MySQL, or external libSQL, set db.mode: none. Provide the connection details yourself and set them as secrets. This setting does not create an external database or move existing data.
db: mode: nonesecrets: items: - name: DATABASE_URL description: "PostgreSQL connection string" required: - all_of: [DATABASE_URL]Django deploys only through this external-database route (Managed SQLite has no backend for the Django ORM). See Framework notes.
Common reasons to use an external database include:
| Situation | Option |
|---|---|
| Data volume or performance needs exceed what Managed SQLite can handle | External PostgreSQL |
| Very high concurrent write volume | PostgreSQL or MySQL |
| Direct access from BI tools or core business systems | Shared PostgreSQL or a data warehouse |
| Backup retention longer than your Keelson plan provides | An external managed database |
Backups and recovery
Section titled “Backups and recovery”All plans include backup and recovery to help recover from accidental deletion or updates.
| Method | Use |
|---|---|
| Daily backups | Restore from an automatically saved daily backup |
| Manual snapshots | Create a backup when needed, such as before a major change (up to 5 per app per day, using UTC dates) |
| Point-in-time recovery (PITR) | Restore data to a selected time within the recovery window |
| Download | Save a backup or data from a selected time locally |
Backup retention and recovery windows
Section titled “Backup retention and recovery windows”| Starter | Plus | Team | |
|---|---|---|---|
| Daily backups retained | 1 | 3 | 7 |
| Point-in-time recovery window | Last 24 hours | Last 7 days | Last 14 days |
Open Restore points on the app’s console page and select a backup or time on the timeline to restore or download it.
Recovery applies to one app and does not affect others. The state before recovery is kept for 72 hours, during which you can undo the recovery. Performing a recovery requires approval in the browser.
What recovery includes
Section titled “What recovery includes”These backups restore only Managed SQLite data. They do not include Files / Media SDK files, external databases, app code, or configuration.
For example, restoring an attachment ID in the database does not bring back a file already deleted from Media. See Update and restore for reverting changes that include code and configuration.
Capacity
Section titled “Capacity”- Managed SQLite has no fixed per-plan capacity limit, but underlying technical limits still apply. App execution may be restricted or suspended if usage significantly exceeds normal business workloads or affects platform stability, other customers, or reasonable operating costs. In emergencies, restrictions may be applied without prior notice.
- Workspace storage has a limit for each plan. It includes Files / Media files, snapshots, deployment files, and similar data. Database capacity is separate.
See Plans and limits for plan-specific limits.
Related pages
Section titled “Related pages”- Files and media
- Scheduled jobs — Share state between the web app and jobs
- keelson.yaml reference