.NET React Templates
Operations

Database and storage

The RDBMS is authoritative for Identity and SaaS metadata. IFileStore is authoritative for uploaded/exported bytes, linked by opaque object keys.

Operations · File storage

Database providers

SQLite is the frictionless default and allows the whole template to recreate from an empty file. It is also supported for a first single-host deployment check when the durable App_Data volume is mounted. The default production policy, Deployment.RequireNetworkDatabase, requires a networked database server.

One variable, DB_PROVIDER, selects the provider rather than a code change, and it selects it in both environments: the Kamal destination that deploys, and the database ./scripts/dev-db.sh up runs on a developer's machine from the same image and initializer scripts. Development therefore happens on the engine that ships. Choose your database is the developer walkthrough; Choose a database covers the Kamal destination layout and how to add another RDBMS.

Configure.Db.cs gives EF Core Identity and OrmLite product data the same connection. Keep their ownership separate even though they share the database:

  • EF Core: users, roles, logins, Identity tokens;
  • OrmLite: organizations, subscriptions, plans, usage, files, audit, lifecycle, operations.

The repository includes ready-to-customize deployment profiles:

  • config/appsettings.deploy.sqlite.example.json — one instance, durable volume, automatic empty-state creation;
  • config/appsettings.deploy.postgres.example.json — PostgreSQL, explicit release migrations, SMTP, and Stripe.

scripts/configure-deployment.sh --provider <name> applies the chosen profile's database policy to the ignored production JSON, so the settings below rarely need editing by hand.

Both providers use the same application code. Set PostgreSQL explicitly with JSON configuration or equivalent environment variables:

Database__Provider=PostgreSql
ConnectionStrings__DefaultConnection='Host=db;Database=acme;Username=acme;Password=...'

Grant the runtime account only required application privileges. Decide whether migrations use the same account or a separately controlled role.

For local screenshots and product tours, ./scripts/seed-example-data.sh writes the same Development-only example dataset through OrmLite on every supported provider. It does not alter the production migration seed, is safe to rerun, and refuses non-Development environments.

Provider setup guides

  • Setup PostgreSQL — provision PostgreSQL 18 with Kamal and replace a disposable SQLite deployment.

Keep provider-specific installation, connection, migration, verification, backup, and recovery instructions on separate setup pages so additional supported RDBMS guides can be added without turning this ownership overview into a platform runbook.

Moving from SQLite to PostgreSQL

For a disposable deployment-path test, discard the SQLite data, provision an empty PostgreSQL database, replace the deployment configuration, and redeploy. The migration task creates the current schema and reference data. Follow Setup PostgreSQL for the complete Kamal accessory, Stripe reset, cutover, verification, and rollback procedure.

For retained customer data, changing Database.Provider is not a data migration. Back up both stores, stop writes, transform and import Identity plus SaaS tables, reconcile row counts and quotas, and only then direct the application to PostgreSQL. Treat uploaded file bytes separately: their object keys remain in the database while the configured IFileStore remains authoritative for content.

Connection and capacity operations

Monitor active connections, pool exhaustion, query latency, locks, deadlocks, storage growth, replication lag, and backup age. Quota admission relies on transactions and conditional updates, so database correctness and lock behavior matter.

Deadlocks, serialization failures, throttling and lost connections are retried by the OrmLiteConfig.RetryPolicy in Configure.Db.cs. Statements the database confirmed weren't applied are run again, while writes whose connection was lost aren't, as they may have been saved. Retries are logged at the Debug level. Use the policy's OnRetry to record them in your metrics, where a rising rate is an early sign of lock contention.

Run maintenance and index analysis using PostgreSQL-native tooling. Avoid direct manual edits to subscription, aggregate, or lifecycle rows; use audited application workflows or a reviewed migration.

File store

LocalFileStore writes under FileStorage.RootPath and is appropriate for development or one durable host. The Kamal example mounts /opt/docker/{service}/App_Data into the container.

For multiple instances or stronger durability, implement IFileStore with shared object storage. Preserve:

  • opaque tenant-safe keys;
  • streaming reads/writes;
  • maximum-size enforcement and hashes;
  • idempotent delete;
  • readiness probing appropriate to the provider;
  • lifecycle/export expiry cleanup.

Uploaded filenames and content types are untrusted metadata. Add malware/content scanning when the derived product serves customer uploads to other users.

Operational SQLite files

ServiceStack jobs and request logs create SQLite files under App_Data/jobs and App_Data/requests. They are operational stores, separate from the main business database. Keep the job store durable if pending work must survive deployment; request logs may use shorter retention or an external diagnostic sink.

Do not copy live SQLite files without a database-aware snapshot method. WAL, database, and shared-memory state must be handled consistently.

On this page