Documentation · Self-Hosting
Self-hosting in production
A deployment is three pieces: a PostgreSQL 16 database, the API (a WebSocket MCP server), and the web app served as static files. This page documents what the January 2027 repository will include — and says so plainly where something is still on the roadmap.
What the repository will include
- Dockerfile.web — multi-stage build: Node 22 + pnpm builds the Vite app, then serves
packages/web/distfrom annginx:alpineimage on port 80. - Dockerfile.api — multi-stage Node 22 image that runs the WebSocket MCP server (
ws-server.tsvia tsx) on port 3001, as the non-rootnodeuser, with a TCP health check. - docker-compose.yml — the PostgreSQL 16 dev database (port 127.0.0.1:5434, named volume, health check).
Not shipped yet — said honestly
There is no production compose file wiring Postgres + API + web together yet, and no Helm chart or TLS termination planned for the initial repository. At launch, operators will run the two images with their own orchestration and place the API behind a reverse proxy (nginx, Caddy, or a cloud load balancer) for TLS. The containers themselves are production-shaped — non-root user, health checks, frozen lockfile installs — but the one-command production stack is a roadmap item, not a shipped feature.
Build and run the pieces
# Web app: build the static bundle
pnpm web:build # tsc -b && vite build -> packages/web/dist
# Or use the images directly
docker build -t openmom-web -f Dockerfile.web .
docker build -t openmom-api -f Dockerfile.api .
docker run -p 8080:80 openmom-web
docker run -p 3001:3001 \
-e DATABASE_URL=postgres://openmom:...@db-host:5432/openmom \
-e OPENMOM_APP_URL=postgres://openmom_app:...@db-host:5432/openmom \
-e NODE_ENV=production \
openmom-api
The API image defaults WS_PORT to 3001 and expects the
two Postgres connection strings (see
Installation
for the role split). The web app is a static SPA — any file server works; the image just
happens to use nginx.
Harden Postgres
Tenant isolation is enforced by the database, not application code — but only if connections arrive as the right role:
- Application traffic must connect as
openmom_app(NOSUPERUSER,NOBYPASSRLS). Tenant-scoped tables run withFORCE ROW LEVEL SECURITY, so even table owners are subject to the policies. - Platform tables (tenants, users, platform admin tables) are invisible or denied to the app role by policy — a compromised app connection cannot read across tenants or mutate platform state, and has no TRUNCATE rights (TRUNCATE ignores RLS).
- Migrations and provisioning connect as the superuser via
DATABASE_URL. Keep that credential out of the API containers. - In production, set
NODE_ENV=production: the server then rejectsOPENMOM_DEV_CALLERat startup and verifies WebSocket origins againstOPENMOM_ALLOWED_ORIGINS(comma-separated list) or the same host.
Backups
OpenMOM is a single Postgres database — standard PostgreSQL backup practice applies. With
the compose volume (pgdata):
docker exec openmom-postgres pg_dump -U openmom -d openmom -Fc > openmom-$(date +%F).dump
docker exec -i openmom-postgres pg_restore -U openmom -d openmom --clean < openmom-2026-01-15.dump
For production, prefer scheduled dumps shipped off-machine plus WAL archiving for
point-in-time recovery — that is your call as the operator; OpenMOM does not ship its own
backup tooling. Because the API never stores card numbers or processor credentials
(payments are last_four + brand + opaque processor token;
third-party credentials live as secret_ref pointers into your
KMS), a database dump does not leak cardholder data — that's a deliberate design
guarantee, not an afterthought.
Upgrades
git pull
pnpm install
pnpm migrate # apply any new OSS migrations
# rebuild + restart the api and web containers
Schema migrations are additive and tracked in
schema_migrations, so re-running
pnpm migrate after an upgrade applies only what's new.
Because the tracker knows what's applied, upgrades are idempotent — and the same rule
keeps any self-hosted deployment migratable onto the managed cloud edition later: the
commercial stream only ever adds its own tables and nullable columns, never alters OSS
behavior.
Repository discussions open with the public repository in January 2027. See the API reference for the documented server surface.