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/dist from an nginx:alpine image on port 80.
  • Dockerfile.api — multi-stage Node 22 image that runs the WebSocket MCP server (ws-server.ts via tsx) on port 3001, as the non-root node user, with a TCP health check.
  • docker-compose.yml — the PostgreSQL 16 dev database (port 127.0.0.1:5434, named volume, health check).
Roadmap

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 with FORCE 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 rejects OPENMOM_DEV_CALLER at startup and verifies WebSocket origins against OPENMOM_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.