Skip to content

Storage layer

The internal/store/ package owns all database access. There is one database for everything (apps, users, metrics, alerts, audit, etc.) so a single backup captures full state.

SQLite with WAL mode. SetMaxOpenConns(4) allows concurrent readers while a single writer serializes through the mode lock. Foreign keys are enabled. Busy timeout is set so transient lock contention retries automatically.

SQL files live in internal/store/migrations/ and are embedded with go:embed. They run in numeric order on startup.

#Topic
001apps
002app_labels
003users, api_keys, user_app_access
004metrics
005request_stats
006webhooks, alert_rules, alert_history
007backup_configs, backup_runs
008compose_hash
009compose_versions, deploy_events
010registries
011db_backup_config, db_backup_runs
012user profile (display_name, email)
013metrics v2
014alert history rule snapshot columns
015backups v2
016indexes (alert_history, backup_runs)
017alert_history.rule_id nullable for gitsync conflict alerts
018gitsync_config (DB-authoritative git sync configuration)
019apps_status_unstable (transient status tracking)
020audit_log (unified activity feed) + audit_config (retention)

Migrations are forward-only. Adding a column with a default is safe; renames and drops are forbidden in published migrations to keep rollback to a previous binary version possible.

The system DB backup uses SQLite’s VACUUM INTO, which writes a consistent snapshot to a target file without blocking writers. “Compact” mode strips metrics and request_stats rows before vacuuming so the snapshot is small enough to ship off-site cheaply. See the system DB backup guide.

The store exposes typed methods (e.g., UpsertApp, ListAlertRules) rather than raw SQL outside the package. This keeps query patterns in one file per resource and makes the API package easy to test.

{data_dir}/simpledeploy.db plus its WAL and SHM siblings. Permissions are 0600.