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
The 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/healthz
none
o3o-online
O3O_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/discovery
The /v1/* API: conversion, o3oscript builds, template filling, extraction (port 8060). Runs as a non-root user.
GET /v1/status
o3o-docbuilder-data
o3o-nextcloud
nextcloud:30-apache
nextcloud profile: a file store to try the integration.
GET /status.php
o3o-nextcloud-html
o3o-nextcloud-db
mariadb:11
Nextcloud database.
o3o-nextcloud-db
o3o-nextcloud-redis
redis:7-alpine
Nextcloud 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.
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.
Public URL of the proxy, no trailing /. Used to build download URLs in callbacks and job results.
O3O_LICENSE_TOKEN
empty
When licensed
License token as a string. Empty means O3O_LICENSE_FILE is read. See Licensing.
O3O_LICENSE_FILE
/etc/o3o/license.token
No
File holding the token. A missing file is normal (community edition). Re-read every 60 seconds.
O3O_LICENSE_PUBKEY
227be50bfccbdc0f…
No
Ed25519 public key (64 hex characters) of the O3O license server. Change only for testing.
O3O_DEV_MODE
0
Must be 0
1 = DEV machine: accepts the TEST key, enables /o3o/demo, returns internal error details in error.detail.
O3O_BUILD_DATE
empty
No
Image build date YYYY-MM-DD (set by the image), compared with the token's upd. Empty skips the check.
O3O_FETCH_ALLOW_HOSTS
empty
Optional
host, 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_SECONDS
30
No
Maximum time for one external URL fetch.
O3O_LOG_LEVEL
info
No
debug, info, warning, error. One JSON line per event on stdout.
TZ
Asia/Ho_Chi_Minh
No
Container time zone. License expiry and daily limits always use UTC+7.
Data folder (volume o3o-gate-data): instance-id, secret.key, embed/, usage/.
O3O_GATE_INTERNAL_URL
http://o3o-gate:8070
No
URL the editing server uses to reach the gate's internal WOPI host. Must match O3O_ONLINE_ALIASGROUP1.
O3O_COOLWSD_URL
http://o3o-online:9980
No
Internal address of the editing server.
O3O_COOLWSD_ADMIN_USER
admin
Should change
Editing server admin account; the gate uses it to read session figures.
O3O_COOLWSD_ADMIN_PASSWORD
none, compose fails when empty
REQUIRED
Admin 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_SECONDS
10
No
Session sampling period, minimum 2.
O3O_GATE_PEAK_WINDOW_SECONDS
300
No
Sliding peak window.
O3O_GATE_RECONNECT_GRACE_SECONDS
120
No
Reconnect grace.
O3O_GATE_CONNECTION_CAP
empty
No
Can only LOWER the cap: effective cap = the smaller of the plan and this value. Used for testing, for example 2.
O3O_GATE_INSTANCE_ID
empty
No
Instance id. Empty means the gate generates inst_ + 16 hex and stores it in /data/instance-id.
O3O_WOPI_ALLOWED_HOSTS
empty
When connecting Nextcloud
Origins 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_KIND
dev-upstream
When using the O3O image
dev-upstream = DEV image, internal testing only; o3o = source-built O3O image. Display only, in /o3o/status.
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.
Variable
Maps to
Default
Change for production
Meaning
O3O_ONLINE_IMAGE
image:
DEV image, digest sha256:1efda304…bf97
When using the O3O image
Editing server image, pinned by digest.
O3O_ONLINE_SERVER_NAME
server_name
localhost:8080
REQUIRED
Public host[:port]; decides the paths in /hosting/discovery.
O3O_ONLINE_ALIASGROUP1
aliasgroup1
http://o3o-gate:8070
No
The gate's internal WOPI host. ALWAYS group 1.
O3O_ONLINE_ALIASGROUP2
aliasgroup2
http://o3o-nextcloud,http://localhost:8081
When connecting Nextcloud
Nextcloud's WOPI host. Items after the comma are regular expressions (write dots as \.).
O3O_COOLWSD_ADMIN_USER
username
admin
Should change
Shared with the gate.
O3O_COOLWSD_ADMIN_PASSWORD
password
none, compose fails when empty
REQUIRED
Shared with the gate. There is no fallback value: when the variable is empty compose reports an error and does not start.
O3O_ONLINE_DICTIONARIES
dictionaries
vi en_US
No
Spell-check dictionaries.
O3O_ONLINE_SSL_TERMINATION
--o:ssl.termination=
false
REQUIRED when the proxy has TLS
true when users arrive over https through an outer proxy.
O3O_ONLINE_FRAME_ANCESTORS
--o:net.frame_ancestors=
empty
REQUIRED when the embedding page is on another origin
ONE value without spaces, for example https://app.example.com. Empty: only the request's own host and the WOPI host are allowed.
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.
Variable
Default
Change for production
Meaning
O3O_GATE_IMAGE
o3o/gate:dev
When pinning a release
Image for o3o-gate. The compose file reads ${O3O_GATE_IMAGE:-o3o/gate:dev}.
O3O_DOCBUILDER_IMAGE
o3o/docbuilder:dev
When pinning a release
Image 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 blockpython ../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 usegrep-E'^O3O_(ONLINE|GATE|DOCBUILDER)_IMAGE=' .env
docker compose --env-file .env -f docker/compose.dev.yml config --images