Quoin docs

Self-hosting

Current status — Quoin's data layer exists, the product does not, and self-hosting is not something you can do yet.

Not yet

There is nothing to self-host today. A growing part of the product exists as real, tested code. It includes sign-in and workspaces, connecting an app and the SDK read path, members and roles, keys and environments, the flag-edit API, metrics from events and the backend of the audit log. Each part has its own account in docs/reports/qn-*.md. "What's built" below summarises them. The hosted service runs at https://app.usequoin.app, but no package or guide lets you run your own copy. This page gives an honest answer to the question. It also records the plan before anyone starts on the rest.

What's decided so far

Quoin's own state runs on Cloudflare Workers and D1. Customer metric data stays in the Postgres database of the customer. Quoin reaches it through Hyperdrive. Quoin never copies it in. There is no payment integration in v1. Every workspace is on the Starter plan (plan = 'starter', the $0 tier). A plan change is an operator command. It is not a checkout flow.

The database of Quoin can move from D1 to Postgres, or to another host. The move stays a driver swap plus a data copy. It does not become a rewrite. These rules make this possible:

  • Procedures only call a repository. They never call the ORM directly.
  • Columns stay portable: text ids, integer epoch-millisecond timestamps, and JSON that is stored as text. There are two named exceptions. The five sign-in tables that Better Auth owns keep its own date columns, and the mirror carries them as a native Postgres timestamp. The 8-byte integers of SQLite become Postgres bigint, so an epoch value cannot overflow.
  • One atomic-write function covers every multi-row write.
  • A parity test keeps a Postgres-shaped mirror schema in lockstep with the schema.

What's built

@quoin/flags is not the only package with real code in the repo. The driver-swap mechanism above is now built and proven in CI. It is not only decided. @quoin/db has these parts:

  • schema.ts (D1) and a schema.pg.ts mirror, which a parity test keeps honest.
  • The runAtomic(ops) atomic-write seam. It has a D1 batch() backend and a Postgres transaction backend.
  • A repository contract test suite. It runs in CI against both D1 (Miniflare) and Postgres (PGlite).

Since QN-01, that suite also covers the first product tables: workspaces, environments and members. It also covers a round trip of export, purge and re-import at workspace granularity. The rows of a workspace come out as NDJSON, one file for each table. They go back in byte-for-byte. The rows of a second workspace stay fenced throughout. The quoin-admin command and the /api/admin/* route that fronts that round trip are mounted and tested. They are gated by QUOIN_ADMIN_TOKEN, and they answer 404 when it is unset. Sign-in itself is real code too. It uses Better Auth, the session cookie quoin.session_token, HttpOnly/SameSite=Lax, and a 30-day expiry. You can reach it at /api/auth/*. See docs/reports/qn-01.md. Since QN-02a, the same contract suite also covers apiKeys, flags, flagRows and flagRevisions on both hosts. It covers key creation and lookup by prefix, the flag rows for each environment and their first-read confirmation, and the revision ordinal that stays gapless under concurrent writers (docs/reports/qn-02a.md).

So far, only PGlite proves the Postgres side. A real multi-connection Postgres environment is still TBD. The driver package that would talk to it is also still TBD. Everything around that mechanism is still missing. There is no deploy tooling and no packaging decision. There is no way to point a running Quoin instance at your own D1 or Postgres. Running Quoin still means that you run the hosted product. The portability guarantee is proven at the level of the data layer. It is not yet shipped as a self-hosting capability.

Since QN-11c, the quoin-admin export/import round trip covers all ten tables, not only the first three. The /api/admin/* route that fronts it covers them too. The ten tables are workspaces, environments, members, apiKeys, flags, flagRows, flagRevisions, experimentExposures, rolloutExposures and auditLog. Each table has one <table>.ndjson file. The byte-for-byte round trip is the same. The round trip of auditLog is proven to leave it untouched (see Audit log). The CLI and the route needed no new code for this. Both already iterate the export object generically. A table that you add to the schema and to the WorkspaceTableName union of the repository therefore appears in the NDJSON output automatically.

What "self-hosting" will mean, once it exists

Nothing here is a commitment beyond the shape that is already decided above:

  • Your own Cloudflare account, and your own D1 database for the state of Quoin.
  • Your own Postgres for the metrics that Quoin reads with SQL templates. If your data is elsewhere (including SQLite or D1), you send events with track()/identify(). Quoin never asks you to move that data.
  • The same quoin CLI and web app, pointed at your own deployment instead of the hosted deployment.

TBD: packaging (a single deploy command, or a documented set of wrangler steps), the name of a self-hosted plan, and whether every hosted feature (AI analysis, guard automation) ships in a self-hosted build on day one.

In the meantime

If you evaluate Quoin for a self-hosted setup, read these two files:

  • docs/07d-decisions.md in the product repository, decisions 2, 14 and 16 specifically. It records the portability guarantees above in full.
  • packages/db/README.md. It states what the team built against those guarantees and what the team deliberately left open.

On this page