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 aschema.pg.tsmirror, which a parity test keeps honest.- The
runAtomic(ops)atomic-write seam. It has a D1batch()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
quoinCLI 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.mdin 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.