How the Uptime Kuma Database Layer Manages SQLite and Optional MySQL/MariaDB
Uptime Kuma stores all persistent data through a thin abstraction built on Knex and RedBean‑Node that defaults to SQLite with WAL mode while supporting external or embedded MariaDB via a JSON configuration file.
The database implementation lives primarily in server/database.js and follows a "default‑to‑SQLite, optional‑MySQL/MariaDB" strategy. This architecture allows single‑node deployments to run without external dependencies while giving production environments a path to scalable, network‑based storage.
Data Directory Bootstrap
When the server starts, Database.initDataDir() creates a configurable data folder (default ./data/). This directory houses the SQLite file, upload assets, screenshots, Docker TLS certificates, and the critical db-config.json that tells the engine which database type to use.
The initialization logic ensures the folder structure exists before any connection attempts occur (lines 57‑62 in server/database.js).
Reading the Database Configuration
Database.readDBConfig() reads db-config.json from the data directory. If the file is missing or malformed, the code transparently falls back to SQLite. The JSON schema supports keys for type, hostname, port, username, password, dbName, and SSL settings (lines 66‑71).
This configuration‑first approach means you can switch database engines without modifying application code—only the JSON file needs to change.
Connection Logic: Three Database Modes
Database.connect() builds a Knex configuration object based on the type field from db-config.json. The implementation branches into three distinct paths.
SQLite (Default)
For type: "sqlite", the layer uses the internal @louislam/sqlite3 dialect. The connection object requires only a filename. The pool is tuned for WAL mode (Write‑Ahead Logging) with up to 20 concurrent connections, and an afterCreate hook runs Database.initSQLite() for every raw SQLite connection (lines 155‑197).
This setup guarantees WAL journaling, foreign‑key enforcement, and PRAGMA tuning optimized for concurrent reads.
External MariaDB/MySQL
When type is "mariadb", the code first opens a direct MySQL connection using mysql.createConnection() to execute CREATE DATABASE IF NOT EXISTS if the target database is missing (lines 188‑199). It then constructs a Knex instance using the mysql2 client with user‑supplied host, port, credentials, and optional SSL.
Pool limits derive from the UPTIME_KUMA_DB_POOL_MAX_CONNECTIONS environment variable (capped at 100). The configuration forces timezone: "Z" and disables automatic DATETIME type‑casting to preserve UTC strings (lines 185‑215).
Embedded MariaDB
The "embedded‑mariadb" type starts an in‑process MariaDB server via the EmbeddedMariaDB class (defined in server/embedded-mariadb.js) and points Knex at its Unix socket. This path reuses the same pool settings as external MariaDB but requires zero external database configuration (lines 239‑247).
SQLite‑Specific Initialization
Database.initSQLite(rawConn, testMode) executes a series of PRAGMA statements on every new connection. In production, it enables WAL mode; in test mode, it switches to MEMORY journaling for speed (lines 12‑18). Additional tuning includes enabling foreign keys, setting cache size to 2000 pages, enabling auto‑vacuum, and setting synchronous mode to NORMAL (lines 20‑27).
These settings ensure durability without sacrificing performance for a monitoring workload that generates frequent writes.
MariaDB‑Specific Bootstrap
For MariaDB connections, the afterCreate pool hook ensures the charset is set to utf8mb4 to support emoji and full Unicode in monitor names and notification text (lines 264‑270). The bootstrap logic also verifies that the database user has sufficient privileges to create the schema if it does not exist, preventing startup failures on fresh installs.
Migration and Patching
Schema changes are handled through Knex migrations stored in db/knex_migrations/. Database.patch() runs R.knex.migrate.latest() against the configured database, temporarily disabling foreign‑key checks for SQLite to avoid constraint violations during column alterations (lines 58‑71).
Migration files are applied uniformly to both SQLite and MariaDB, ensuring consistent schema versions regardless of the underlying engine.
Graceful Shutdown
Database.close() implements clean teardown for both database types. For SQLite, it flushes the WAL to the main database using PRAGMA wal_checkpoint(TRUNCATE) before closing connections. It then loops until all RedBean‑Node promises resolve, preventing data loss during container shutdowns or process restarts (lines 36‑53).
Practical Implementation Examples
Bootstrapping SQLite (Default Behavior)
const Database = require("./server/database");
// Initialize the data folder structure
Database.initDataDir({ "data-dir": "./data/" });
// Connect with autoload models enabled
await Database.connect(false, true, false);
// Query using RedBean-Node
const monitors = await Database.R.getAll("SELECT * FROM monitor");
Switching to External MariaDB
Create ./data/db-config.json:
{
"type": "mariadb",
"hostname": "db.example.com",
"port": "3306",
"username": "kuma_user",
"password": "secret",
"dbName": "kuma",
"ssl": false
}
Then connect:
await Database.connect();
// Automatically creates database if missing and applies migrations
Running Migrations Manually
// After version upgrades or schema changes
await Database.patch();
Summary
- Uptime Kuma centralized all database logic in
server/database.js, using Knex for query building and RedBean‑Node for ORM operations. - The system defaults to SQLite with WAL mode enabled, storing data in
./data/kuma.dbwith optimized PRAGMA settings for monitoring workloads. - Switching to MySQL/MariaDB requires only a
db-config.jsonfile; the code automatically creates the database, setsutf8mb4charset, and manages connection pooling via environment variables. - Embedded MariaDB provides a zero‑config option for development environments that need MySQL compatibility without external services.
- Schema migrations live in
db/knex_migrations/and apply uniformly across all supported engines viaDatabase.patch(). - Shutdown logic ensures SQLite WAL checkpoints complete before process exit, preventing database corruption.
Frequently Asked Questions
What is the default database used by Uptime Kuma?
SQLite is the default database engine. On first startup, Uptime Kuma creates ./data/kuma.db using the @louislam/sqlite3 dialect with WAL mode enabled. This requires no configuration and works immediately for single‑instance deployments.
How do I migrate from SQLite to MySQL or MariaDB?
Create a db-config.json file in your data directory specifying "type": "mariadb" along with host, port, and credentials. Restart the application; it will automatically create the target database and run migrations. You must manually migrate existing data using export/import tools, as Uptime Kuma does not provide an automatic data migration utility between engines.
What is embedded MariaDB in Uptime Kuma?
Embedded MariaDB is an in‑process database server shipped with Uptime Kuma (defined in server/embedded-mariadb.js). When configured with "type": "embedded-mariadb", the application starts a local MariaDB instance bound to a Unix socket, giving you MySQL compatibility without requiring a separate database container or installation.
How does Uptime Kuma handle database schema updates?
Uptime Kuma uses Knex migrations stored in db/knex_migrations/. During startup, Database.patch() executes knex.migrate.latest() to apply any pending schema changes. This works identically for SQLite and MariaDB, ensuring the application always runs the correct schema version regardless of the underlying database type.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →