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

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.

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 caseStorage
Create, search, and update customer records, projects, or requestsManaged SQLite
Read and write configuration files or records of processed data inside the appFiles SDK
Display images or let users open PDFs and attachmentsMedia 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.

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.

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.

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.

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.

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()
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"] });

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.

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.

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: none
secrets:
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:

SituationOption
Data volume or performance needs exceed what Managed SQLite can handleExternal PostgreSQL
Very high concurrent write volumePostgreSQL or MySQL
Direct access from BI tools or core business systemsShared PostgreSQL or a data warehouse
Backup retention longer than your Keelson plan providesAn external managed database

All plans include backup and recovery to help recover from accidental deletion or updates.

MethodUse
Daily backupsRestore from an automatically saved daily backup
Manual snapshotsCreate 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
DownloadSave a backup or data from a selected time locally
StarterPlusTeam
Daily backups retained137
Point-in-time recovery windowLast 24 hoursLast 7 daysLast 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.

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.

  • 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.