Get started
Architecture and components Beta
Four services behind a single entry point: the nginx proxy, the editing server, o3o-gate and DocBuilder. Routing, the three main data flows, connection counting, networks, volumes and the variables to change for production.
This page describes a beta feature: it works but may still change.
On this page
Version 1 favours few moving parts and reliability: o3o-gate and DocBuilder are written in Python, the DocBuilder queue lives in memory and each service is one container. No O3O code is patched into the editing server; the services talk to each other over HTTP and WebSocket.
Services#
| Service | Port | Built on | Responsibility | Health check |
|---|---|---|---|---|
o3o-proxy | 8080 | nginx:1.27-alpine | Single entry point: routing, auth_request to o3o-gate, blocks the editing server's admin paths, lets traffic through if o3o-gate does not answer. | GET /o3o/healthz via the proxy |
o3o-online | 9980 | coolwsd (MPL 2.0 code) | Collaborative editing in the browser. | GET /readyz |
o3o-gate | 8070 | Python 3.12, FastAPI | Session counting, connection cap, license token, api.js, embed sessions, internal WOPI host, callbacks. | GET /o3o/healthz |
o3o-docbuilder | 8060 | Debian bookworm, LibreOffice, FastAPI | REST API /v1/*: conversion, o3oscript builds, templates, extraction. | GET /v1/status |
o3o-nextcloud and database | 80 to 8081 | nextcloud:30-apache, mariadb:11, redis:7-alpine | Optional nextcloud profile for trying the integration. | GET /status.php |
Proxy routing#
| Path | Goes to | Notes |
|---|---|---|
/browser/<hash>/cool.html | o3o-online | Through auth_request, stage page. When refused, o3o-gate returns the connection-limit page. |
/cool/<document>/ws, /cool/ws?WOPISrc=… and every other WebSocket form | o3o-online | WebSocket, through auth_request, stage ws. WebSocket requests o3o-gate cannot parse are refused. |
/browser, /cool/, /hosting, /lool/ | o3o-online | Not checked by o3o-gate; WebSocket upgrades are not forwarded. |
/browser/dist/admin, /cool/adminws, /cool/getMetrics | 404 | The editing server's admin console and metrics are not exposed, trailing-slash forms included. |
/o3o/ | o3o-gate | /o3o/auth and /o3o/wopi/ return 404 from outside. |
/v1/ | o3o-docbuilder | Request bodies are not buffered; DocBuilder enforces file size limits per plan. |
/ | 302 to /o3o/ |
Opening a document through Nextcloud#
- The administrator installs the
richdocumentsconnector app, then runsrichdocuments:activate-configwith-w http://o3o-online:9980(the address Nextcloud uses to reach the editing server) and-c http://o3o-nextcloud(the address the editing server uses to call Nextcloud back, which becomes the origin ofWOPISrc). Nextcloud reads/hosting/discoveryto learn the editor page path. - A user clicks a file. Nextcloud builds an iframe and posts a form to
cool.htmlwithWOPISrcandaccess_token. - The proxy asks o3o-gate: if there is room the editor loads; if not the user sees the limit page with buttons to open read-only or retry.
- The editor opens a WebSocket; the proxy asks o3o-gate again at the
wsstage. - The editing server calls Nextcloud back over WOPI: CheckFileInfo, GetFile, and PutFile on save. o3o-gate is not on the save path, so it can never block a save.
Direct embedding flow#
- Your page loads
/o3o/api.jsand callsnew O3O.Editor(id, config). api.jssendsPOST /o3o/embed/session. o3o-gate checks the origin and the JWT, downloadsdocument.urlunder the SSRF rules, applies the connection cap and returns the editor address and anaccess_token.api.jsbuilds the iframe and posts the form;WOPISrcpoints at o3o-gate's internal WOPI host.- The editing server calls o3o-gate over the internal network to read and write the file. On each save o3o-gate stores a new version, answers immediately, then sends an HMAC-signed callback to
callbackUrl. - The browser receives events through
postMessage. When everyone has left the document for 30 seconds, o3o-gate sends aclosedcallback and keeps the file for 24 hours.
DocBuilder flow#
- Your server calls
/v1/convert,/v1/build,/v1/template/renderor/v1/extract/*withAuthorization: Bearer. - DocBuilder authenticates, resolves the plan from the license token, applies the rate limit, validates the parameters (o3oscript scripts against the JSON Schema), downloads the source URL if any, then queues the job in memory.
- An idle worker (a dedicated headless LibreOffice process) runs the job. Macros never run: files loaded through LibreOffice always have macros disabled, and
/v1/template/renderrejects templates that contain macros with422 macro_not_allowed. On timeout the worker is stopped and rebuilt and the job fails withtimeout. - Synchronous mode returns the file directly. Asynchronous mode returns
202with a job; pollGET /v1/jobs/{id}and download fromGET /v1/files/{id}.
Connection counting#
One connection is one concurrent editing session. One person with 3 documents open for editing is 3 connections; three people editing one document are 3 connections. o3o-gate samples editing sessions every 10 seconds from the editing server's internal admin channel, reports a 5-minute sliding peak for reconciliation and allows reconnection within 120 seconds even at the cap.
- Open sessions are never cut off, even when the cap is lowered (expired or replaced token).
- Saves are never blocked: o3o-gate is not on Nextcloud's save path, and embedded saves ignore the cap.
- At the cap: new sessions through Nextcloud see the limit page; new embedded sessions open read-only with a notice banner.
- Counting errors or an unresponsive editing server: o3o-gate lets traffic through (fail-open) and logs it. If o3o-gate is down entirely, nginx lets traffic through as well.
- Every 5 minutes o3o-gate appends a line to
/data/usage/usage-YYYY-MM.csvso you can reconcile usage yourself.
Networks, volumes and ports#
- One bridge network,
o3o-net; services reach each other by service name. - Volumes:
o3o-gate-data,o3o-docbuilder-data,o3o-nextcloud-html,o3o-nextcloud-db. - Only two ports are published:
O3O_PROXY_PORT(default 8080) andO3O_NEXTCLOUD_PORT(default 8081, only with the nextcloud profile). - The editing server needs
cap_add: [MKNOD]; its image has no shell, so debug withdocker logs.
Variables to change for production#
Environment variables
O3O_PUBLIC_URLURLrequiredDefault:http://localhost:8080Public URL of the proxy, without a trailing/. Used to build download URLs in callbacks and jobs.O3O_ONLINE_SERVER_NAMEhost[:port]requiredDefault:localhost:8080Public host name; determinesurlsrcin/hosting/discovery.O3O_ONLINE_SSL_TERMINATIONtrue | falserequiredDefault:falseSet totruewhen users connect over https (TLS is handled by the proxy in front).O3O_ONLINE_FRAME_ANCESTORSoriginoptionalOrigin of the embedding page when it differs from the server, one value without spaces.O3O_COOLWSD_ADMIN_PASSWORDstringrequiredInternal admin password of the editing server; o3o-gate uses it to count sessions. There is no fallback value: compose refuses to start while the variable is empty, and the gate withO3O_DEV_MODE=0refuses to start while the password is empty or stillCHANGE_ME.O3O_EMBED_JWT_SECRETstring ≥ 32requiredHS256 key that verifies signed embed configurations.O3O_EMBED_CALLBACK_SECRETstringrequiredHMAC SHA-256 key that signs save callbacks.O3O_EMBED_ALLOWED_ORIGINSlistrequiredDefault:*Origins allowed to embed; do not leave it as*in production.O3O_DOCBUILDER_API_KEYSlistrequiredDocBuilder API keys, asname:keyorkey, 24 to 128 characters.O3O_DEV_MODE0 | 1requiredDefault:0Must be0in production (disables the test page and internal error details).O3O_EMBED_ALLOW_UNSIGNED0 | 1requiredDefault:0Must be0in production (JWT signing required).
The complete list of variables and defaults is in Self-hosting.
DEV image and release build#
| DEV mode (v1) | O3O release build | |
|---|---|---|
| Editing image | Upstream image pinned by digest (26.04.4.1.1) | Built from source; build files are written but experimental, not built yet |
| Status | Runs, verified | Coming soon |
| Branding | Upstream name and logo, not changeable by configuration | O3O Office Online name; white-label for the enterprise edition (coming soon) |
| Cap built into the image | 20 connections, 10 documents | Real cap enforced by o3o-gate per plan |
GET /o3o/status | dev_image: true | dev_image: false |
Several editing servers behind one o3o-gate, WOPI proof key synchronisation, oCIS support, a DocBuilder queue that survives restarts, DocBuilder on the O3O 26.8 core instead of Debian's LibreOffice, Prometheus metrics.
Performance and hardware#
Not measured yet. The documentation gives no figures for processing time, requests per second, memory or minimum hardware per connection count; these will be published once real measurements exist on defined hardware.
Licensing#
The editing server is open source under MPL 2.0; when the source-built O3O image is released, any upstream file O3O modifies will be published to recipients of that image (coming soon). o3o-gate, api.js and the proxy configuration are standalone O3O code that talks to the editing server only over HTTP and WebSocket.
DocBuilder's own code is owned by O3O and does not modify LibreOffice code, but it is not standalone in the same way: it loads LibreOffice through the UNO bridge (python3-uno), and its image is distributed WITH LibreOffice 7.4 installed from the Debian bookworm archive, mostly under MPL 2.0 with some files under LGPL and Apache 2.0. Anyone distributing the DocBuilder image must therefore keep those packages' license notices (the image already carries /usr/share/doc/<package>/copyright) and tell recipients where to obtain the matching LibreOffice source. The obligations are detailed in the license section of Building from source.