Search page titles, headings and text across the docs.

canvist Cloud

On this page

Self-hosting

In this guide you run canvist Cloud’s server yourself: the same binary canvist Cloud runs, free, with no caps. You’ll start it, create a project, configure it, and learn how it stores documents and scales.

1. Start the server

The server is one binary, canvist-cloud, in services/cloud. Build its image from the repository root, then run it with a token secret, an admin token, and a Postgres database:

docker build -f services/cloud/Dockerfile -t canvist-cloud .
docker run -p 8080:8080 \
  -e CANVIST_TOKEN_SECRET="$(openssl rand -hex 32)" \
  -e CANVIST_ADMIN_TOKEN="$(openssl rand -hex 32)" \
  -e DATABASE_URL=postgres://user:password@db.internal:5432/canvist \
  canvist-cloud

Or run it from source:

CANVIST_TOKEN_SECRET=$(openssl rand -hex 32) \
CANVIST_ADMIN_TOKEN=$(openssl rand -hex 32) \
DATABASE_URL=postgres://user:password@localhost:5432/canvist \
cargo run --release -p canvist_cloud

Without DATABASE_URL, documents live in memory and vanish on restart, which is only useful for trying it out. Database migrations run on startup.

2. Create a project

Create a project with the admin token. It returns one API key per environment, shown once:

curl -X POST http://localhost:8080/v1/admin/projects \
  -H "Authorization: Bearer $CANVIST_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "my-app", "environments": ["production", "staging"] }'

New projects start on the free plan’s 1,000-document cap. Remove the cap:

curl -X PUT http://localhost:8080/v1/admin/projects/$PROJECT/terms \
  -H "Authorization: Bearer $CANVIST_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "plan": "enterprise" }'

Other admin routes: POST /v1/admin/projects/{project}/keys with { "environment": "staging" } adds a key, and GET /v1/admin/stats shows loaded documents and connected clients.

Then follow Sync and Convert with localhost:8080 as the host.

Configuration

VariableDefaultMeaning
CANVIST_TOKEN_SECRETrequiredHMAC key for client tokens, at least 32 bytes. Rotating it signs everyone out.
DATABASE_URLmemoryPostgres connection string. Connections are unencrypted: use a private network or a TLS proxy.
CANVIST_ADMIN_TOKENoffBearer token for /v1/admin.
HOST, PORT::, 8080Where to listen. :: accepts IPv4 and IPv6.
CANVIST_ROLEallall, sync, or convert: which route groups this instance serves.
CANVIST_IDLE_EVICTION_SECONDS60How long a document with no connections stays in memory.
CANVIST_FLUSH_INTERVAL_MS250How often changes are written to Postgres.
CANVIST_COMPACT_AFTER_UPDATES200Update rows after which a document is folded into a snapshot.
CANVIST_MAX_MESSAGE_BYTES1048576The largest WebSocket message and REST body.
CANVIST_MAX_DOCUMENT_BYTES16777216The largest document, as encoded Yjs state.
CANVIST_WEBHOOK_DEBOUNCE_MS2000Quiet time before a document.updated webhook.
CANVIST_WEBHOOK_MAX_WAIT_MS30000The longest a webhook waits while changes keep coming.
CANVIST_WEBHOOK_ALLOW_PRIVATEfalseAllow HTTP and private hosts as webhook targets, inside your network.
CANVIST_CONVERT_MAX_BYTES8388608The largest conversion request.
CANVIST_CONVERT_TIMEOUT_MS10000The longest a conversion may run before the request gives up.
CANVIST_CONVERT_CONCURRENCYtwice the CPUsConversions running at once.
CANVIST_PAUSE_RECHECK_SECONDS30How often a session paused by a full free plan checks again.
STRIPE_SECRET_KEYoffA Stripe test-mode key (sk_test_ or rk_test_); live keys are refused.
STRIPE_WEBHOOK_SECREToffThe Stripe webhook endpoint’s signing secret; with the key, turns on POST /v1/stripe/webhook.
STRIPE_METER_EVENTcanvist_active_documentThe monthly active document meter’s event name.
STRIPE_CONVERSION_METER_EVENTcanvist_conversionThe conversion meter’s event name.
STRIPE_REPORT_INTERVAL_MS10000How often unreported usage is sent to Stripe.
STRIPE_AUTOMATIC_TAXfalseCalculate tax in Checkout. Only with a head office address and an active Stripe Tax registration.
CANVIST_LOG_FORMATtextjson for structured logs.
RUST_LOGinfoLog filter, such as canvist_cloud=debug.

To bill your own customers through Stripe, set the STRIPE_* variables on one service per database. Test-mode keys only: the server refuses live keys.

How documents are stored

Changes are written behind: every flush interval, all edited documents are saved in one transaction, and folded into a snapshot once enough build up, when a document goes idle, and at shutdown. If the process dies between flushes, clients send the missing changes again when they reconnect, because every sync session starts by exchanging what each side lacks.

On SIGTERM, the server stops reporting ready, asks every client to reconnect (close code 1012), and saves every loaded document. Give it at least 15 seconds before a hard kill.

Scaling

Each instance holds the documents it serves in memory, so every client of one document must reach the same instance. Run one sync instance, or route by document name. Conversions are stateless and scale out freely.

What one instance handles, measured on Apple silicon:

MeasureMeasured
One keystroke to the last of 100 peers0.6 ms
Applying one keystroke1.5 µs
Saving 1,000 edited documents to Postgres20 ms
Memory per idle connection17 KiB
Memory per loaded 50-paragraph document132 KiB
10,000 paragraphs, Markdown to HTML14 ms

A loaded document costs about 2.4 KiB per block, plus 10 KiB. Documents stay in memory only while someone is connected, so memory follows concurrent documents, not total ones.

Docs