The Database
Chinaski stores everything in one SQLite file per instance (no database server, no credentials, no network protocol) and keeps its schema current with idempotent migrations that run automatically at startup.
One file per instance
The database is a single chinaski.db file, by default at the application root, or wherever your CHINASKI_DB environment variable points. Every instance you run (chinaski@1, chinaski@prod, a staging copy) has its own file and its own settings; the production unit templates derive these paths per instance name so copies never collide.
Connections funnel through one shared module with a canonical option set: errors surface immediately, text is Unicode-clean, and a 5-second busy timeout keeps a locked file from hanging a request. WAL mode is set persistently, so readers don’t block writers and the typical workload (one active admin session plus periodic builds) stays snappy. Health checks on the /health-style endpoint verify read, write, and journal mode, warning when WAL isn’t active.
How the schema evolves
- Fresh installs are seeded by
schema.sql, the complete schema for a new database; the install scripts run it for you (or you can runsqlite3 chinaski.db < schema.sqlby hand). - The numbered files under
migrations/are not applied mechanically to running instances. They document the evolution and serve fresh installs only. - Already-deployed instances update through idempotent migration subs wired into a single startup routine. Each migration introspects the database first (does the column or table already exist?) and does nothing when it does. First-boot cases skip these entirely because
schema.sqlcreated the fresh shape.
Because these run at startup and are idempotent, upgrading Chinaski is bring-the-service-up-and-go: the instance heals its own schema on the way.
What’s inside
Everything the CMS manages: pages, posts, blocks, snippets, stacks, categories, redirects, navigation, forms with their submissions, media metadata, documents, users and sessions, the auth/audit/event logs, the build log with validation reports, the render cache, key-value settings, revision histories, and backup run records.
Two confidentiality notes worth knowing:
- Credentials you enter in settings (SMTP passwords, API keys, storage secrets) are stored encrypted in the database, keyed off your instance secret, never in plaintext.
- A schema version stamp ties the file’s layout to the code’s expectations, and it’s the same number the backup/restore flow verifies: a database restored onto a differently-versioned instance is refused rather than partially applied.
Health and housekeeping
Housekeeping is built in: expired password-reset tokens, dead sessions, and bounded log histories are pruned automatically at startup and on periodic timers. The /health endpoint verifies the database can be read and written and reports the journal mode, so drift from the expected configuration is caught before it bites.
Backups are ordinary file copies, no special tooling required, and the dedicated backup pages cover snapshots, retention, and restore.