Profpatsch/users/Profpatsch/sqlite-apps
- README.md 16.6 KiB
- default.nix 276 B
- go.mod 600 B
- go.sum 3.2 KiB
- hello-world.nix 1.8 KiB
- main.go 116.4 KiB
SQLite-Embedded Self-Hostable Applications
Overview
This project explores a novel approach to application distribution and deployment where complete applications are embedded within SQLite database files. Users can deploy applications by simply drag-and-dropping database files onto a web interface, which automatically extracts and runs the embedded application code while using the same SQLite file for data storage.
Core Concept
Problem: Traditional application deployment involves complex dependency management, installation procedures, and separate concerns for application code, configuration, and data storage.
Solution: Embed compressed squashfs images of Nix store paths into SQLite database files, then dynamically generate OCI runtime bundles at execution time, creating atomic, portable, self-contained applications with optimal storage efficiency.
Key Benefits
For Users
- Zero Installation: Drop a database file, get a running application
- Atomic Backup: Single file contains both code and data
- Version Control: Git can track complete application state
- Portability: Email/copy complete working applications
- No Dependency Hell: Squashfs images with Nix ensure hermetic, reproducible dependencies
- Optimal Storage: Zstd compression in squashfs achieves superior compression ratios
For Developers
- Simple Distribution: One file per application release
- Guaranteed Reproducibility: Squashfs images are deterministic and bit-identical
- Built-in Observability: Structured logging via file descriptors
- Native Isolation: OCI runtime provides PID, network, mount, IPC, and UTS namespaces
- Easy Updates: Replace embedded bundle, restart container
Architecture
SQLite Schema
Each application database contains a single system table managed by the runtime:
-- Single table for SQLite-embedded applications
-- squashfs_data MUST be the last column for efficient reading
CREATE TABLE __app (
name TEXT NOT NULL,
version TEXT NOT NULL,
entry_point TEXT NOT NULL, -- Executable path within squashfs
description TEXT,
author TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
squashfs_data BLOB NOT NULL -- Compressed squashfs image (zstd)
);
-- All other tables are created and managed by the application itself
-- Applications handle their own schema initialization and migrations
Runtime Architecture
┌─────────────────────────────────────────────────────────────────┐
│ Host System │
│ │
│ ┌─────────────────┐ │
│ │ Web Interface │ ← Drag & drop .db files │
│ │ (Runtime) │ │
│ └─────────┬───────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Application Instance │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Squashfs │ │ runc │ │ SQLite │ │ │
│ │ │ Extract + │ │ Container │ │ Database │ │ │
│ │ │Dynamic OCI │ │ (rootless) │ │(bind-mounted)│ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ Container features: │ │
│ │ • User namespace isolation (uid/gid mapping) │ │
│ │ • PID/Network/Mount/IPC/UTS namespace isolation │ │
│ │ • Database bind-mounted to /data/db.sqlite │ │
│ │ • Squashfs extracted directly to rootfs │ │
│ │ • stdout/stderr inherited for simplicity │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Component Interaction Flow
- Deployment: User runs
sqlite-apps run timetracker.db - Extraction: Runtime extracts squashfs image from
__apptable to temp file - Extract:
unsquashfsextracts squashfs directly into OCI bundle rootfs - Prepare: OCI config.json generated dynamically with proper user namespace mappings
- Execution: runc spawns rootless container with extracted rootfs + database bind-mounted
- Operation: App reads/writes data tables using standard SQLite database
- Cleanup: Temporary files cleaned up automatically on exit
Implementation Components
1. Application Packager
Purpose: Convert Nix derivations into SQLite-embedded applications
# Build the tool
nix-build ~/kot/Profpatsch -A users.Profpatsch.sqlite-apps
# Usage
sqlite-apps package timetracker my-timetracker.nix timetracker.db
# With options
sqlite-apps package --verbose -d "Time tracking app" -a "Author" timetracker ./timetracker.nix timetracker.db
# Process:
# 1. nix-build derivation to get store paths
# 2. nix copy complete closure to temporary nix store
# 3. mksquashfs temp_nix_store app.squashfs -comp zstd
# 4. CREATE TABLE __app (..., squashfs_data BLOB)
# 5. INSERT INTO __app (..., squashfs_data) VALUES (..., readfile('app.squashfs'))
Input: App name, Nix derivation, output filename Output: SQLite database with embedded squashfs image (no application schema)
Key Principle: Packager is schema-agnostic - applications manage their own data structures
2. Application Runtime
Purpose: Extract and execute SQLite-embedded applications
# Usage
sqlite-apps run timetracker.db
# With options
sqlite-apps run --verbose --keep-temp timetracker.db
# Process:
# 1. SELECT entry_point, squashfs_data FROM __app
# 2. Extract squashfs to /tmp/app-{timestamp}/app.squashfs
# 3. unsquashfs -f -d bundle/rootfs app.squashfs (direct extraction)
# 4. Generate OCI config.json with rootless user mappings + database bind mount
# 5. runc --rootless run with extracted rootfs + database mounted to /data/db.sqlite
# 6. Application handles its own schema init/migration on startup
# 7. Cleanup temporary files automatically on exit
Input: SQLite database with embedded application Output: Running OCI container instance with observability
Key Principle: Runtime is schema-agnostic - extracts squashfs, generates OCI config, and runs rootless container
3. Web Management Interface
Purpose: User-friendly application deployment and management
Features:
- Drag-and-drop deployment of .db files
- Application instance status monitoring
- Log viewing and trace exploration
- Application lifecycle management (start/stop/restart)
- Resource usage monitoring
4. Example Application: Timetracker
Purpose: Reference implementation demonstrating the pattern
Features:
- Time entry recording with client/project categorization
- Invoice generation using Typst templates
- Varlink service for CLI integration
- Web interface for data management
- OTEL tracing for all operations
- Self-managing schema: Creates and migrates its own database tables on startup
Security Model
Isolation Mechanisms
- User Namespace Isolation: Applications run in rootless containers with uid/gid mapping (container uid 0 → host uid 1000)
- Filesystem Isolation: Applications run with chroot to extracted squashfs rootfs, cannot access host filesystem
- Process Isolation: PID namespace isolation prevents seeing/interacting with host processes
- Network Isolation: Applications have no network access by default (no network namespace configured)
- IPC/UTS Isolation: Mount, IPC, and UTS namespaces provide additional isolation boundaries
Trust Boundaries
- Container Runtime: runc provides OCI-compliant isolation with kernel namespace support
- Rootless Execution: No privileged operations required, runs as regular user
- Filesystem Containment: Applications cannot escape squashfs rootfs boundary
- Database Access: Applications can only access bind-mounted SQLite file at /data/db.sqlite
Current Limitations
- No resource limits: No CPU/memory limits currently enforced
- No capability dropping: Full capabilities within user namespace
- No seccomp: No system call filtering implemented
- Manual cleanup: Temporary files require manual cleanup on failure
Observability
Structured Logging
Applications emit OTEL-compatible JSON traces to file descriptor 5:
{
"timestamp": "2024-01-15T10:30:45.123Z",
"trace_id": "abc123...",
"span_id": "def456...",
"operation": "time_entry.create",
"duration_ms": 45,
"attributes": {
"client": "Acme Corp",
"hours": 2.5
}
}
Monitoring Integration
- Trace Collection: Runtime forwards fd 5 output to OTEL collector
- Metrics Derivation: Standard metrics derived from trace data
- Health Checking: Periodic application health probes
- Resource Monitoring: systemd provides CPU/memory/IO metrics
Development Workflow
Application Development
- Standard Development: Build applications using normal Nix/language tooling
- Packaging: Use packager to embed into SQLite database
- Testing: Deploy locally using runtime for integration testing
- Distribution: Share single .db file containing complete application
Runtime Development
- Component Testing: Test packager, runtime, and web interface separately
- Integration Testing: End-to-end tests with example applications
- Security Testing: Verify isolation and sandboxing mechanisms
- Performance Testing: Measure startup times, resource usage, throughput
Use Cases
This system is designed for true self-hosting - applications that work out-of-the-box without the complexity of production database management, logging infrastructure, or backup strategies. Applications must be designed to work within embedded database capabilities and scale appropriately.
Perfect Fit: Personal and Small Group Applications
- Personal note-taking app - All notes in the database, no external dependencies
- Family calendar service - Shared calendars for 3-5 people, single-file storage
- Friend group blog - Simple multi-author blog with embedded data storage
- Personal ActivityPub server - Single-user Mastodon alternative, all data self-contained
- Small team time tracker - 2-10 people tracking hours, integrated invoicing
- Personal media library - Catalog movies/books/music with metadata storage
- Home automation dashboard - IoT sensor data and controls for one household
- Personal finance tracker - Bank transactions, budgets, reports in single file
Scaling Pattern: Multiple Instances Instead of Bigger Infrastructure
- Multiple calendar apps - Different friend groups get separate calendar instances
- Per-project time trackers - Each client gets their own tracking app instance
- Topic-specific blogs - Separate blog instances for different subjects
- Family member apps - Each person gets their own note-taking/tracking instance
Explicitly NOT Suitable For
- Mastodon instance - Requires PostgreSQL, complex migrations, federation complexity
- Multi-tenant SaaS - Applications serving hundreds or thousands of users
- E-commerce platforms - Complex inventory, payments, compliance requirements
- Enterprise applications - Requiring dedicated DBAs, complex deployment pipelines
- Real-time collaboration - Google Docs-style apps needing operational transform
- High-traffic websites - Applications requiring load balancing, CDNs, caching layers
Design Principles for Compatible Applications
- Database-first: Design data models that work well with embedded database capabilities
- Self-contained: No external service dependencies (Redis, Elasticsearch, etc.)
- Reasonable scale: Designed for 1-20 users, not hundreds
- Simple migrations: Schema changes that can be applied automatically
- Graceful degradation: Work offline, handle temporary database locks
- Backup-friendly: All application state contained in the single database file
Implementation Roadmap
Phase 1: Core Runtime (MVP)
- Basic packager (Nix → SQLite + squashfs) - COMPLETE: Uses nix copy + mksquashfs
- Basic runtime (SQLite → running container) - COMPLETE: Uses unsquashfs + dynamic OCI config + runc
- Simple hello-world example application - COMPLETE: Demonstrates basic functionality
- Manual deployment workflow - COMPLETE: CLI tools package-app and run-sqlite-app
Remaining Phase 1 Tasks
- Build system integration - Rewritten in Go with
default.nixfor nix-build - Error handling improvements - More user-friendly error messages and validation
- Automated testing - Unit and integration tests for packager/runtime
- Usage documentation - Examples, troubleshooting guide, and common patterns
Phase 2: Web Interface (Not Started)
- Web runtime server - HTTP server to manage applications via REST API
- Drag-and-drop deployment - Upload .db files via web interface
- Application management - Start/stop/restart applications through web UI
- Monitoring dashboard - View running applications and their status
- Log viewing - Display application stdout/stderr in web interface
- Application lifecycle - Persistent application management with systemd integration
Phase 3: Advanced Features (Not Started)
- OTEL trace collection - Structured observability with trace aggregation
- Security hardening - More restrictive container policies and sandboxing
- Performance optimization - Faster startup, better resource usage
- Application marketplace - Registry/discovery of available applications
- Networking support - Expose application ports to host system
- Configuration management - Pass environment variables and config to applications
Phase 4: Production Features (Not Started)
- Application updates - Update running applications without data loss
- Backup/restore tooling - Application data management and migration
- Development tooling - Templates and scaffolding for new applications
- Documentation and tutorials - Comprehensive guides and examples
- Community application examples - Reference implementations and patterns
- Integration with existing tools - Kubernetes, Docker Compose, etc.
Technical Considerations
Performance
- Startup Time: Squashfs extraction + OCI config generation adds ~200ms overhead
- Runtime Performance: No performance penalty once running, native OCI isolation
- Storage Efficiency: Zstd compression achieves ~30% compression ratio (229MB → 68MB for hello-world)
- Memory Usage: Each container instance requires ~5MB base overhead (rootless runc + extracted rootfs)
- SQLite Efficiency: Single table with BLOB as last column optimizes read performance
Limitations
- Large Applications: Multi-GB applications may be impractical to embed
- Dynamic Dependencies: Applications requiring runtime package installation won't work
- Shared Resources: No mechanism for sharing code between applications
- Platform Support: Currently Linux-only due to OCI runtime dependencies (runc)
Future Enhancements
- Container Image Registry: Push/pull SQLite apps from container registries
- Shared Runtime: Deduplicate common dependencies across applications
- Hot Updates: Update applications without full container restart
- Clustering: Distribute applications across multiple hosts using K8s/podman