Skip to content
Memtru
Documentation menu
Guides

Deployment

The selected hosted beta topology is now Vercel + Supabase PostgreSQL. Follow the deployment and cutover runbook. The single-host SQLite instructions below remain available for local/self-hosted use. Historical audit reports describe the earlier SQLite-only candidate.

For local/self-hosted use, the web application can run as a single Next.js server with an embedded SQLite database. It has not been deployed to a public environment as part of this release; the steps below are what has been verified locally.

Requirements

  • Node.js 24.x (tested on 24.14). node:sqlite must be available.
  • A persistent disk for the database file.
  • TLS termination in front of the server (a reverse proxy or platform load balancer).

Build and run

cd apps/web
npm ci
npm run build
DATABASE_PATH=/var/lib/memtru/memtru.db \
MEMTRU_PUBLIC_URL=https://memtru.example.com \
MEMTRU_SIGNUP=closed \
PORT=3000 npm start

Migrations run automatically when the server opens the database. To run them explicitly: npm run db:migrate.

Environment variables

Variable Default Purpose
DATABASE_PATH data/memtru.db SQLite file location.
MEMTRU_PUBLIC_URL derived from request URL shown in CLI instructions.
MEMTRU_SIGNUP closed in production, open in development Only literal open enables registration; use closed for private beta.
MEMTRU_SIGNUP_LIMIT_PER_HOUR 10 Sign-ups allowed per client address per hour.
RESEND_API_KEY + MEMTRU_EMAIL_FROM unset Resend API key and a sender on a verified domain (for example Memtru <hello@memtru.com>). When both are set, new accounts must confirm their email before using the dashboard or API. Without them verification is not enforced.
MEMTRU_EMAIL_OUTBOX unset Local testing only: write emails as JSON files to this directory instead of sending them (also enables verification). Ignored on Vercel.
PORT 3000 Listen port.
MEMTRU_INSECURE_COOKIES unset Set to 1 only for local production builds served over plain HTTP (end-to-end tests). Never in a real deployment.

There are no secrets to configure: sessions and tokens are random values stored as hashes.

Operations

  • Backups: on the tested Node 24 runtime, run npm run db:backup -- /secure/backups/new-snapshot.db with the same DATABASE_PATH as the server. The script uses SQLite's online backup API, includes committed WAL state, checks integrity, and refuses to overwrite files. A live .db file copy alone is unsafe in WAL mode. Apply backup retention separately. Other supported Node versions must verify backup API availability or use SQLite's .backup command.
  • Password resets: npm run admin:reset-password -- user@example.com (reads the new password from stdin and signs the user out everywhere).
  • Health check: GET /api/health.
  • Logs: unexpected errors are logged as one JSON line with a request_id, also returned to the client as X-Request-Id.

Scaling beyond one SQLite process

SQLite mode still supports one process only. The selected Vercel deployment instead uses the implemented PostgreSQL connection and shared limiter. Follow the hosted beta runbook and DECISIONS.md D-005. Database writes remain serialized for this controlled beta; no throughput target has been established.

Optional single-host SQLite topology

Use exactly one Node server process on one host with an attached persistent local disk. Do not use cluster workers, multiple app replicas, ephemeral serverless filesystems, a shared network SQLite file or automatic horizontal scaling. WAL, foreign keys and a 5-second busy timeout are configured by openDatabase. Writes use synchronous BEGIN IMMEDIATE transactions; report authorization is revalidated inside the same transaction as insertion. This model is credible for a small, controlled cohort, but hosted durability and load have not been measured.

Keep the database and backups outside the checkout and web root, accessible only to the service/operator accounts. Use Node 24, TLS termination, a fixed MEMTRU_PUBLIC_URL, and MEMTRU_SIGNUP=closed. A trusted reverse proxy must overwrite incoming X-Forwarded-For, X-Real-IP, X-Forwarded-Host and X-Forwarded-Proto; reject unexpected Host headers and prevent direct access to the app port. In-process rate limits reset on restart and cannot enforce limits across replicas.

Private beta enrollment and recovery

Keep public registration closed. After verifying the recipient's identity out of band, run:

npm run admin:provision -- beta-recipient@example.test "Beta Recipient"

Supply a unique 12–200 character initial password through protected stdin, never as a command-line argument, source file, log or checked-in environment value. Administrators must use a protected terminal; ordinary interactive stdin can echo input. Deliver credentials over a verified private channel. The script creates an account and an audit event, without organization membership. Existing owners grant the correct organization in settings. The first trusted operator can sign in and create an organization through onboarding.

There is no self-service password reset or automated invitation. Verify identity before an administrator runs admin:reset-password; resets invalidate all sessions. Removing organization membership permanently revokes that creator's tokens for the organization, including direct administrative membership deletion. Rejoining requires new tokens. Existing sessions retain access only to still-authorized organizations.

Backup, restore and rollback procedure

Choose an owner, backup interval, retention, recovery point objective and recovery time objective before hosting; none are guaranteed by this repository. Copy successful snapshots to encrypted, restricted off-host storage, and monitor backup age, integrity failures and disk capacity. The synthetic recovery regression restores reports and API authorization from a snapshot while the original WAL database remains open. This is a local drill, not proof of host/volume disaster recovery.

To restore, stop incoming traffic and the single server, retain the failed database for investigation, and select a verified snapshot. Restore to a new path, run PRAGMA integrity_check and PRAGMA foreign_key_check, then start the candidate with DATABASE_PATH pointing to the restored file. Reconcile membership/token revocations and password changes since the snapshot before reopening traffic; old backups can restore credentials previously revoked. Test sign-in, a synthetic report upload, cross-organization denial and a removed member's denial. Preserve unrelated evidence and backups.

Back up before starting a new version: migrations run automatically at database open. Migration 0002_membership_token_revocation adds an index/trigger and revokes already orphaned credentials. It does not delete customer reports. Roll back application code only to a version retaining the membership and ingestion checks; reverting to the vulnerable baseline is not an acceptable authorization rollback. Do not drop the revocation trigger or clear revoked_at to restore service. If a database restore is unavoidable, apply current migrations and reconcile revocations while traffic remains stopped.

PostgreSQL migration decision

Dimension SQLite now Managed PostgreSQL for multiple instances
Development / migration cost Existing implementation, no data conversion Async query interface, SQL/placeholder changes, timestamps, backfill/export and parity tests
Authorization Service checks, membership trigger and transactional upload recheck Same service checks; optional RLS with verified application roles and connection context
Transactions One local writer, BEGIN IMMEDIATE Explicit transactions and row locks; token/member changes serialized with ingestion
Connections Embedded single server Bounded pooling, transaction-safe per-tenant context, credentials from a secret manager
Durability Persistent volume plus operator-managed snapshots Managed backups/PITR, still requiring restore drills and revocation reconciliation
Rate limits Process memory, restart loss Shared durable or centralized rate-limit store
Operations / cost Host, persistent disk, encrypted backup storage Database plan, storage, backup/PITR, pooler and shared limiter; price before purchase

Move before introducing a second application instance, failover requirements beyond the selected recovery plan, or measured write contention/latency that the single host cannot satisfy. No measured concurrency ceiling is claimed. The async abstraction and PostgreSQL parity tests now exist; SQLite remains available locally. Migrate in a maintenance window with a verified snapshot, versioned PostgreSQL schema, foreign keys/indexes, explicit tenant checks, upload idempotency constraints, fixture export/import validation and a read-only cutover rehearsal. Never dual-write customer data without a reconciled rollback design. The current PostgreSQL implementation and migration procedure are documented in the hosted beta runbook. No remote migration has been performed.

Monitoring and rollout gates

Capture structured error logs with request ids in restricted storage; do not collect request bodies or memory content. Monitor health availability, 5xx/error codes, disk space, backup freshness and upload failures. Audit events currently accumulate without pruning, and report retention runs on ingestion. Establish an operator-owned retention schedule for logs, audit events and backups. A shared Sentry/email/feedback service is not configured; collect beta feedback through the verified onboarding channel using run ids and sanitized reports, never real memory contents.

Before hosting, verify proxy headers/cookies on the actual TLS origin, closed enrollment, restore/revocation reconciliation, authorized provider targets, resource limits and final CI. Do not enable MEMTRU_INSECURE_COOKIES outside localhost tests. No infrastructure was purchased or deployed.