Skip to content

Self-hosting

Compose file and environment variables Beta

Every service in the O3O Office Online compose file and all O3O_* environment variables, defaults and what to change for production.

This page describes a beta feature: it works but may still change.

On this page

The v1 Docker bundle is ONE compose file: online/docker/compose.dev.yml, with a built-in nextcloud profile. All configuration lives in online/.env, created with bash scripts/gen-secrets.sh --dev (PowerShell: scripts\gen-secrets.ps1 -Dev): the script copies online/.env.example and generates the secrets; production machines leave out --dev. Every command below runs inside online/.

BashEveryday commands
cd online
DC="docker compose --env-file .env -f docker/compose.dev.yml"

$DC up -d                      # start the four core services
$DC --profile nextcloud up -d  # add Nextcloud, MariaDB, Redis
$DC ps                         # status and health
$DC logs -f o3o-gate           # follow one service's logs
$DC down                       # stop, keep data
$DC --profile nextcloud down -v   # stop and DELETE volumes (data loss)

# Run a second copy on the same host: another project (-p), other ports
O3O_PROXY_PORT=18080 O3O_NEXTCLOUD_PORT=18081 docker compose -p o3otest --env-file .env -f docker/compose.dev.yml up -d

Services#

ServiceImageRoleHealth checkData
o3o-proxynginx:1.27-alpine + online/proxy/nginx.confThe single entry point (port 8080): routing, asks the gate before opening an editing session, blocks the editing server's admin paths, lets traffic through when the gate is down.GET /o3o/healthznone
o3o-onlineO3O_ONLINE_IMAGE (DEV image pinned by digest)Real-time co-editing server (port 9980). Needs cap_add: [MKNOD]. The image has no shell: debug through its logs.built into the image; through the proxy: GET /hosting/discoverykeeps no documents
o3o-gateO3O_GATE_IMAGE, default o3o/gate:dev (python:3.12-slim-bookworm)Connection counting and capping, license token, O3O.Editor embedding, internal WOPI host, callbacks (port 8070).GET /o3o/healthzo3o-gate-data
o3o-docbuilderO3O_DOCBUILDER_IMAGE, default o3o/docbuilder:dev (debian:bookworm-slim)The /v1/* API: conversion, o3oscript builds, template filling, extraction (port 8060). Runs as a non-root user.GET /v1/statuso3o-docbuilder-data
o3o-nextcloudnextcloud:30-apachenextcloud profile: a file store to try the integration.GET /status.phpo3o-nextcloud-html
o3o-nextcloud-dbmariadb:11Nextcloud database.o3o-nextcloud-db
o3o-nextcloud-redisredis:7-alpineNextcloud cache and file locking.none
  • A single bridge network o3o-net; services call each other by service name.
  • The compose file sets no container_name, so several projects (-p) can run side by side on one host.
  • The gate and DocBuilder images are built with the online/ context so spec/plans.json is copied to /app/plans.json; plan limits are always read from that file.
  • DocBuilder runs with no-new-privileges, a memory limit and pids_limit; document macros are disabled outright when files are loaded through LibreOffice, and POST /v1/template/render rejects templates that contain macros with 422 macro_not_allowed.
  • Fonts in DocBuilder: Liberation, Carlito, Caladea, DejaVu, Noto; enough for Vietnamese and metric-compatible with Arial, Times New Roman, Calibri and Cambria.

Environment variables#

  • Every O3O service reads its configuration from O3O_-prefixed variables. Booleans: 1 is on, 0 is off.
  • Lists are comma separated, without extra spaces.
  • Values containing CHANGE_ME count as NOT SET: the service still starts, logs a WARNING and disables the feature that needs that secret.
  • The one exception is O3O_COOLWSD_ADMIN_PASSWORD: without it the gate cannot count connections and the editing server's admin console uses a password anyone can guess. The compose file has no fallback value for this variable and refuses to start while it is empty or unset; the gate in production mode (O3O_DEV_MODE=0) refuses to start while the password is empty or still CHANGE_ME. Run bash scripts/gen-secrets.sh to generate it. On a DEV machine (O3O_DEV_MODE=1) the gate still starts with a warning, but does not count connections.
  • The "Change for production" column: REQUIRED means the default is unsafe or wrong outside a DEV machine.

Shared by the gate and DocBuilder#

VariableDefaultChange for productionMeaning
O3O_PUBLIC_URLhttp://localhost:8080REQUIREDPublic URL of the proxy, no trailing /. Used to build download URLs in callbacks and job results.
O3O_LICENSE_TOKENemptyWhen licensedLicense token as a string. Empty means O3O_LICENSE_FILE is read. See Licensing.
O3O_LICENSE_FILE/etc/o3o/license.tokenNoFile holding the token. A missing file is normal (community edition). Re-read every 60 seconds.
O3O_LICENSE_PUBKEY227be50bfccbdc0f…NoEd25519 public key (64 hex characters) of the O3O license server. Change only for testing.
O3O_DEV_MODE0Must be 01 = DEV machine: accepts the TEST key, enables /o3o/demo, returns internal error details in error.detail.
O3O_BUILD_DATEemptyNoImage build date YYYY-MM-DD (set by the image), compared with the token's upd. Empty skips the check.
O3O_FETCH_ALLOW_HOSTSemptyOptionalhost, host:port or CIDR allowed even when internal (SSRF exception). Name entries are trusted by name and resolved at connect time; CIDR entries still pin the checked IP. DEV: host.docker.internal,o3o-nextcloud.
O3O_FETCH_TIMEOUT_SECONDS30NoMaximum time for one external URL fetch.
O3O_LOG_LEVELinfoNodebug, info, warning, error. One JSON line per event on stdout.
TZAsia/Ho_Chi_MinhNoContainer time zone. License expiry and daily limits always use UTC+7.

o3o-gate#

VariableDefaultChange for productionMeaning
O3O_GATE_PORT8070NoListening port inside the container.
O3O_GATE_DATA_DIR/dataNoData folder (volume o3o-gate-data): instance-id, secret.key, embed/, usage/.
O3O_GATE_INTERNAL_URLhttp://o3o-gate:8070NoURL the editing server uses to reach the gate's internal WOPI host. Must match O3O_ONLINE_ALIASGROUP1.
O3O_COOLWSD_URLhttp://o3o-online:9980NoInternal address of the editing server.
O3O_COOLWSD_ADMIN_USERadminShould changeEditing server admin account; the gate uses it to read session figures.
O3O_COOLWSD_ADMIN_PASSWORDnone, compose fails when emptyREQUIREDAdmin password, shared with o3o-online. There is no fallback value: compose refuses to start while the variable is empty or unset; the gate with O3O_DEV_MODE=0 refuses to start while the password is empty or still CHANGE_ME. If it is set but does not match the editing server, the gate cannot count and lets everything through (upstream.coolwsd = "auth_failed", connections.source = "none").
O3O_GATE_SAMPLE_SECONDS10NoSession sampling period, minimum 2.
O3O_GATE_PEAK_WINDOW_SECONDS300NoSliding peak window.
O3O_GATE_RECONNECT_GRACE_SECONDS120NoReconnect grace.
O3O_GATE_CONNECTION_CAPemptyNoCan only LOWER the cap: effective cap = the smaller of the plan and this value. Used for testing, for example 2.
O3O_GATE_INSTANCE_IDemptyNoInstance id. Empty means the gate generates inst_ + 16 hex and stores it in /data/instance-id.
O3O_WOPI_ALLOWED_HOSTSemptyWhen connecting NextcloudOrigins of external WOPI hosts the gate may query once the cap is reached, to recognise read-only sessions. Example http://o3o-nextcloud.
O3O_ONLINE_IMAGE_KINDdev-upstreamWhen using the O3O imagedev-upstream = DEV image, internal testing only; o3o = source-built O3O image. Display only, in /o3o/status.
O3O_EMBED_ENABLED1No0 disables embedding: /o3o/embed/*, /o3o/wopi/*, /o3o/api.js return 404.
O3O_EMBED_JWT_SECRETemptyREQUIREDHS256 key (at least 32 characters) for config.token. Empty with unsigned mode off: 401 embed_auth_not_configured.
O3O_EMBED_ALLOW_UNSIGNED0Must be 01 = accept unsigned configs. DEV machines only.
O3O_EMBED_ALLOWED_ORIGINS*Should changeComma-separated origins allowed to embed; * = any. Used for CORS and PostMessageOrigin.
O3O_EMBED_CALLBACK_SECRETemptyREQUIREDHMAC SHA-256 key for document save callbacks. Empty means unsigned callbacks.
O3O_EMBED_MAX_FILE_MB100NoMaximum embedded document size.
O3O_EMBED_SESSION_TTL_MINUTES720NoLifetime of the embed session access_token.
O3O_EMBED_PENDING_TTL_SECONDS300NoA created session the editor never loads within this time is cancelled.
O3O_EMBED_RETAIN_HOURS24NoHow long the last working copy is kept after closing.

o3o-docbuilder#

VariableDefaultChange for productionMeaning
O3O_DOCBUILDER_PORT8060NoListening port inside the container.
O3O_DOCBUILDER_DATA_DIR/dataNoData folder (volume o3o-docbuilder-data): jobs/, files/, profiles/.
O3O_DOCBUILDER_API_KEYSemptyREQUIREDAPI keys, name:key or key, comma separated. Empty: every authenticated endpoint returns 401.
O3O_DOCBUILDER_JWT_SECRETemptyOptionalHS256 key (at least 32 characters); when set, JWTs are accepted too.
O3O_DOCBUILDER_WORKERSmin(số nhân, 4)OptionalNumber of document processes, 1 to 32.
O3O_DOCBUILDER_MAX_FILE_MBemptyNoCan only LOWER the plan's file size limit.
O3O_DOCBUILDER_SYNC_TIMEOUT_SECONDS60NoMaximum wait of synchronous mode.
O3O_DOCBUILDER_JOB_TIMEOUT_SECONDSemptyNoCan only LOWER the plan's job timeout.
O3O_DOCBUILDER_CALLBACK_SECRETemptyWhen using callbacksHMAC SHA-256 key for job callbacks.
O3O_DOCBUILDER_WORKER_MAX_JOBS200NoRecycle a worker after this many jobs.
O3O_DOCBUILDER_WORKER_MAX_AGE_MINUTES60NoRecycle a worker after this many minutes.
O3O_DOCBUILDER_SOFFICE/usr/bin/sofficeNoPath of the document processing program in the image.

o3o-online#

The editing server image does not read O3O_* variables; the compose file maps them to the names the image understands. Two values are fixed in the compose file: DONT_GEN_SSL_CERT=YES and the extra_params below.

VariableMaps toDefaultChange for productionMeaning
O3O_ONLINE_IMAGEimage:DEV image, digest sha256:1efda304…bf97When using the O3O imageEditing server image, pinned by digest.
O3O_ONLINE_SERVER_NAMEserver_namelocalhost:8080REQUIREDPublic host[:port]; decides the paths in /hosting/discovery.
O3O_ONLINE_ALIASGROUP1aliasgroup1http://o3o-gate:8070NoThe gate's internal WOPI host. ALWAYS group 1.
O3O_ONLINE_ALIASGROUP2aliasgroup2http://o3o-nextcloud,http://localhost:8081When connecting NextcloudNextcloud's WOPI host. Items after the comma are regular expressions (write dots as \.).
O3O_COOLWSD_ADMIN_USERusernameadminShould changeShared with the gate.
O3O_COOLWSD_ADMIN_PASSWORDpasswordnone, compose fails when emptyREQUIREDShared with the gate. There is no fallback value: when the variable is empty compose reports an error and does not start.
O3O_ONLINE_DICTIONARIESdictionariesvi en_USNoSpell-check dictionaries.
O3O_ONLINE_SSL_TERMINATION--o:ssl.termination=falseREQUIRED when the proxy has TLStrue when users arrive over https through an outer proxy.
O3O_ONLINE_FRAME_ANCESTORS--o:net.frame_ancestors=emptyREQUIRED when the embedding page is on another originONE value without spaces, for example https://app.example.com. Empty: only the request's own host and the WOPI host are allowed.
BashThe v1 extra_params value (one line)
--o:ssl.enable=false --o:ssl.termination=${O3O_ONLINE_SSL_TERMINATION} --o:welcome.enable=false --o:net.frame_ancestors=${O3O_ONLINE_FRAME_ANCESTORS} --o:security.enable_metrics_unauthenticated=false

Pinning the gate and DocBuilder images#

The compose file does not hard-code the gate and DocBuilder image tags; it reads the two variables below, and when they are empty it uses the locally built tags o3o/gate:dev and o3o/docbuilder:dev. The python tools/release.py env command (run at the repository root, or python ../tools/release.py env from online/) generates the version block of .env from versions.json: O3O_ONLINE_IMAGE, O3O_ONLINE_IMAGE_KIND, O3O_GATE_IMAGE and O3O_DOCBUILDER_IMAGE; with --write it replaces the old block in the file and keeps the previous file as .env.bak.

VariableDefaultChange for productionMeaning
O3O_GATE_IMAGEo3o/gate:devWhen pinning a releaseImage for o3o-gate. The compose file reads ${O3O_GATE_IMAGE:-o3o/gate:dev}.
O3O_DOCBUILDER_IMAGEo3o/docbuilder:devWhen pinning a releaseImage for o3o-docbuilder. The compose file reads ${O3O_DOCBUILDER_IMAGE:-o3o/docbuilder:dev}.
BashGenerate the .env version block from versions.json
cd online
# Preview the version block
python ../tools/release.py env

# Write it into .env (the old block is replaced, the previous file is kept as .env.bak)
python ../tools/release.py env --write .env

# Check the images compose will use
grep -E '^O3O_(ONLINE|GATE|DOCBUILDER)_IMAGE=' .env
docker compose --env-file .env -f docker/compose.dev.yml config --images

o3o-proxy and the nextcloud profile#

VariableDefaultChange for productionMeaning
O3O_PROXY_PORT8080OptionalProxy port published on the host.
O3O_NEXTCLOUD_PORT8081OptionalNextcloud port published on the host (nextcloud profile).
O3O_NC_ADMIN_USERadminShould changeNextcloud administrator.
O3O_NC_ADMIN_PASSWORDCHANGE_MEREQUIREDNextcloud administrator password.
O3O_NC_DB_PASSWORDCHANGE_MEREQUIREDNextcloud database password.
O3O_NC_DB_ROOT_PASSWORDCHANGE_MEREQUIREDMariaDB root password.
O3O_NC_TRUSTED_DOMAINSlocalhost localhost:8081 o3o-nextcloudREQUIREDNextcloud trusted domains, separated by spaces.