Production Deployment & Configuration
This page focuses on production deployment and configuration. For evaluation, see Quick Start.
Deployment Architecture & Components

Version Notes
- v0.13.0 introduced
collaboration-helperfor snapshot and other CPU-intensive tasks. - v0.15.0 introduced tiered collaboration scheduling to route sheets by size.
Services
- collaboration-server: Collaboration engine implementing OT on the server
- collaboration-helper: CPU-intensive tasks like snapshot generation (v0.13.0+)
- universer: Document operations and collaboration routing
- exchange worker: Import/export worker (CPU and memory intensive)
collaboration-server is stateful. Edits for a document are routed to the same instance to improve performance and stability.
Dependencies
- RDS: document metadata and permissions
- Object Storage: document content, images, and blocks
- Redis: collaborator cache and rate limiting
- RabbitMQ: collaboration message broadcast
- Temporal: import/export workflow engine
Deployment Options
| Option | Best for | Notes |
|---|---|---|
| Docker Compose | Small scale, no K8s | Single-node, vertical scaling only |
| K8s | Medium/large scale | Horizontal scaling and elasticity |
Production Checklist
- Identity & Permissions: do you need USIP integration?
- Capacity planning: CPU/memory for collaboration and exchange
- Infrastructure: prefer self-managed or cloud-managed components
- Data safety: backup strategy for RDS and object storage
- Observability: metrics and logs (see SRE Manual)
Configuration Entry Points
Docker Compose
Create .env.custom in the install directory to override defaults from .env.
K8s
Override Helm chart defaults via your own values.yaml (do not edit chart files directly).
Common Configurations
Enable USIP (Identity & Permissions)
# usip
USIP_ENABLED=true # set true to enable USIP
USIP_URI_CREDENTIAL=https://your-domain/usip/credential
USIP_URI_USERINFO=https://your-domain/usip/userinfo
USIP_URI_ROLE=https://your-domain/usip/role
USIP_URI_COLLABORATORS=https://your-domain/usip/collaborators
USIP_URI_UNITEDITTIME=https://your-domain/usip/unit-edit-time
# Apikey config is optinal.
USIP_APIKEY=
# auth
AUTH_PERMISSION_ENABLE_OBJ_INHERIT=false
AUTH_PERMISSION_CUSTOMER_STRATEGIES=universer:
config:
usip:
enabled: true
uri:
userinfo: 'https://your-domain/usip/userinfo'
collaborators: 'https://your-domain/usip/collaborators'
role: 'https://your-domain/usip/role'
credential: 'https://your-domain/usip/credential'
unitEditTime: 'https://your-domain/usip/unit-edit-time'
# apikey is optional.
apikey: ''
auth:
permission:
enableObjInherit: false
customerStrategies: ''Field notes:
USIP_ENABLED/usip.enabled: enable USIP and configure all endpoints.USIP_URI_*: endpoint URLs for credential, userinfo, role, collaborators, unit-edit-time.AUTH_PERMISSION_*: permission point settings and object inheritance. See USIP.
Enable Event Sync
EVENT_SYNC=true # set true to enableuniverser:
config:
rabbitmq:
eventSync: true # set true to enableField notes:
- Once enabled, you need a consumer to process events (see Event Sync).
Use Self-Managed Infrastructure
We recommend self-managed or cloud-managed components for better reliability and data safety.
Compatibility Requirements
- MQ: AMQP/AMQPS compatible, RabbitMQ recommended. Built-in image:
rabbitmq:3-management. - Redis: must support core commands (GET, MGET, SET, SETNX, DEL, EXISTS, EXPIRE, HSET, HGET, HDEL, HGETALL, HLEN, SCAN, Pipeline, EVAL, EVALSHA).
- RDS: PostgreSQL 16.1, MySQL 8.0, GaussDB, DamengDB. Distributed databases requiring sharding are not supported; TiDB and similar are compatible but may have hotspot risks due to monotonic IDs.
- Object Storage: AWS S3 compatible. Built-in image:
bitnami/minio:2024.8.3-debian-12-r1.
RDS
Note: Temporal does not support GaussDB or DamengDB; Temporal will use a separate PostgreSQL.
PostgreSQL-compatible
DISABLE_UNIVER_RDS=true
DATABASE_DRIVER=postgresql
DATABASE_HOST=your-database-host
DATABASE_PORT=your-database-port
DATABASE_DBNAME=univer
DATABASE_USERNAME=user-name
DATABASE_PASSWORD=passwordpostgresql:
enabled: false
universer:
config:
database:
driver: postgresql
host: your-database-host
port: your-database-port
dbname: univer
username: postgres
password: postgres
temporal:
server:
config:
persistence:
default:
driver: sql
sql:
driver: postgres12
host: your-database-host
port: your-database-port
database: temporal
user: postgres
password: postgres
visibility:
driver: sql
sql:
driver: postgres12
host: your-database-host
port: your-database-port
database: temporal_visibility
user: postgres
password: postgresField notes:
DISABLE_UNIVER_RDS: disable built-in database when using your own RDS.DATABASE_DBNAMEmust match the database name in the init scripts.DATABASE_USERNAMEshould have select/insert/update/delete privileges.- Temporal databases are only used for import/export workflows, not document data.
driver: postgres12requires PostgreSQL 12+.
MySQL-compatible
DISABLE_UNIVER_RDS=true
DATABASE_DRIVER=mysql
DATABASE_HOST=your-database-host
DATABASE_PORT=your-database-port
DATABASE_DBNAME=univer
DATABASE_USERNAME=user-name
DATABASE_PASSWORD=passwordpostgresql:
enabled: false
universer:
config:
database:
driver: mysql
host: your-database-host
port: your-database-port
dbname: univer
username: mysql
password: mysql
temporal:
server:
config:
persistence:
default:
driver: sql
sql:
driver: mysql8
host: your-database-host
port: your-database-port
database: temporal
user: mysql
password: mysql
visibility:
driver: sql
sql:
driver: mysql8
host: your-database-host
port: your-database-port
database: temporal_visibility
user: mysql
password: mysqlField notes:
DATABASE_DRIVER=mysqlmaps to MySQL 8.x.driver: mysql8requires MySQL 8.x.- Other fields match the PostgreSQL setup.
GaussDB
DISABLE_UNIVER_RDS=true
DATABASE_DRIVER=gaussdb
DATABASE_HOST=your-database-host
DATABASE_PORT=your-database-port
DATABASE_DBNAME=univer
DATABASE_USERNAME=user-name
DATABASE_PASSWORD=passworduniverser:
config:
database:
driver: gaussdb
host: your-database-host
port: your-database-port
dbname: univer
username: gaussdb
password: gaussdbField notes:
- GaussDB is not supported by Temporal, so no Temporal config is needed.
- Other fields match the PostgreSQL setup.
DamengDB
DISABLE_UNIVER_RDS=true
DATABASE_DRIVER=dameng
DATABASE_HOST=your-database-host
DATABASE_PORT=your-database-port
DATABASE_DBNAME=univer
DATABASE_USERNAME=user-name
DATABASE_PASSWORD=passworduniverser:
config:
database:
driver: dameng
host: your-database-host
port: your-database-port
dbname: univer
username: dameng
password: damengField notes:
- DamengDB is not supported by Temporal, so no Temporal config is needed.
- Other fields match the PostgreSQL setup.
Redis
DISABLE_UNIVER_REDIS=true
REDIS_ADDR=host:port[,host:port]
REDIS_USERNAME=user-name
REDIS_PASSWORD=password
REDIS_DB=0
REDIS_TLS_ENABLED=false
REDIS_TLS_INSECURE=false
REDIS_TLS_CA=
REDIS_TLS_CERT=
REDIS_TLS_KEY=universer:
config:
redis:
poolSize: 100
addr: 192.168.1.100:6379
read_timeout: 1s
write_timeout: 1s
db: 0
username: user_name_here
password: password_here
tlsConfig:
enabled: false
insecure: false
ca: ''
cert: ''
key: ''
worker:
redis:
poolSize: 10
addr: 192.168.1.100:6379
read_timeout: 1s
write_timeout: 1s
db: 0
username: user_name_here
password: password_here
tlsConfig:
enabled: false
insecure: false
ca: ''
cert: ''
key: ''
redis:
enabled: falseField notes:
DISABLE_UNIVER_REDIS: disable built-in Redis when using your own.REDIS_ADDR: comma-separated list for Redis cluster.- TLS modes: insecure, CA-only, or mTLS.
- For Docker Compose, CA/cert/key can be file paths or content; if paths, use container-mounted paths.
- For K8s,
tlsConfigtypically uses inline content. worker.redisshould matchuniverser.config.redis.
MQ
DISABLE_UNIVER_MQ=true
RABBITMQ_CLUSTER_ENABLED=true
RABBITMQ_CLUSTER_USERNAME=user-name
RABBITMQ_CLUSTER_PASSWORD=password
RABBITMQ_CLUSTER_ADDR=host:port[,host:port]
RABBITMQ_CLUSTER_VHOST=/
RABBITMQ_CLUSTER_SCHEMA=amqpuniverser:
config:
rabbitmq:
cluster:
enabled: true
addr: '192.168.1.2:5672,192.168.1.5:5672'
username: user-here
password: password-here
vhost: /
schema: amqp
rabbitmq:
enabled: falseField notes:
DISABLE_UNIVER_MQ: disable built-in RabbitMQ when using your own.RABBITMQ_CLUSTER_ENABLEDmust be true.RABBITMQ_CLUSTER_ADDRsupports multiple addresses; each must be readable and writable.RABBITMQ_CLUSTER_VHOSTdefaults to/.RABBITMQ_CLUSTER_SCHEMAisamqporamqps.
Object Storage
DISABLE_UNIVER_S3=true
S3_USER=user
S3_PASSWORD=password
S3_REGION=your-inner-s3like-region
S3_PATH_STYLE=true|false
S3_ENDPOINT=inner-visit-host:port
S3_ENDPOINT_PUBLIC=public-visit-host:port
S3_DEFAULT_BUCKET=default-bucket-nameuniverser:
config:
s3:
accessKeyID: admin
accessKeySecret: minioadmin
region: us-east-1
endpoint: http://192.168.1.100:9000
endpointPublic: http://192.168.1.100:9001
usePathStyle: true
defaultBucket: univer
minio:
enabled: falseField notes:
DISABLE_UNIVER_S3: disable built-in MinIO when using your own.S3_PATH_STYLE:truefor Path-Style,falsefor Virtual-Host Style.S3_ENDPOINT: internal endpoint for backend services.S3_ENDPOINT_PUBLIC: public endpoint for client downloads.S3_DEFAULT_BUCKET: default bucket name.
Enable Observability
ENABLE_UNIVER_OBSERVABILITY=true
GRAFANA_USERNAME=set-your-admin-user-name-here
GRAFANA_PASSWORD=set-your-admin-user-password-here
HOST_GRAFANA_PORT=set-the-port-you-wantFor K8s, install the observability stack separately. See SRE Manual.
Field notes:
ENABLE_UNIVER_OBSERVABILITY=trueenables built-in Grafana/Prometheus.HOST_GRAFANA_PORTis the external Grafana port.
Capacity & Scaling
UNIVERSER_REPLICATION_CNT=2
COLLABORATION_SERVER_REPLICATION_CNT=2
COLLABORATION_SERVER_MEMORY_LIMIT=2048
COLLABORATION_HELPER_REPLICATION_CNT=2
COLLABORATION_HELPER_MEMORY_LIMIT=2048
EXCHANGE_WORKER_REPLICATION_CNT=1
EXCHANGE_WORKER_MEMORY_LIMIT=4096
EXCHANGE_WORKER_IMPORT_CONCURRENT=1
EXCHANGE_WORKER_EXPORT_CONCURRENT=1universer:
replicaCount: 3
collaboration-server:
replicaCount: 3
maxMemoryLimit: 2048
collaboration-helper-server:
replicaCount: 2
worker:
replicaCount: 1
temporalWorker:
importConcurrent: 1
exportConcurrent: 1Field notes:
*_REPLICATION_CNT/replicaCount: number of instances.*_MEMORY_LIMIT/maxMemoryLimit: memory limit in MB.EXCHANGE_WORKER_*_CONCURRENT: concurrency per worker.
Tiered Collaboration Scheduling (Advanced)
v0.15.0 adds tiered scheduling to route documents by size to different collaboration clusters. It is disabled by default.
Example:
unitRoutingConf:
tiers:
- tierName: normal
enable: true
upgradeCellsThreshold: 0
downgradeCellsThreshold: 0
upgradeImportTimeThreshold: 0
- tierName: large
enable: true
upgradeCellsThreshold: 1000
downgradeCellsThreshold: 900
upgradeImportTimeThreshold: 10
- tierName: huge
enable: true
upgradeCellsThreshold: 10000
downgradeCellsThreshold: 9000
upgradeImportTimeThreshold: 60Field notes:
tiers: list of cluster tiers.enable: enable or ignore a tier.upgradeCellsThreshold: min cells to upgrade to the tier.downgradeCellsThreshold: downgrade threshold to avoid flapping.upgradeImportTimeThreshold: import-time threshold for new sheets.- At least one tier must have all thresholds set to 0.
Scheduling example:
- A new workbook with 0 cells goes to
normal. - At 1000 cells, it upgrades to
large. - If it drops to 899 cells, it downgrades to
normal. - A workbook imported in 61 seconds goes to
huge.
Docker Compose ships with two tiers (normal/large):
ENABLE_LARGE_TIER_COLLABORATION_SERVER=false
LARGE_TIER_MIN_CELLS_COUNT=2000000
LARGE_TIER_DOWNGRADE_CELLS_COUNT=1950000
LARGE_TIER_MIN_IMPORT_SECONDS=10
LARGE_TIER_COLLABORATION_SERVER_REPLICATION_CNT=2
LARGE_TIER_COLLABORATION_SERVER_MEMORY_LIMIT=8192Configure multi-tier clusters and routing rules yourself.
Network & CORS
DOCKER_NETWORK_SUBNET=172.30.0.0/16
HOST_NGINX_PORT=the-univer-server-api-port-you-wanted
HOST_MINIO_PORT=the-minio-port-you-wanted
HOST_GRAFANA_PORT=the-grafana-port-you-wanted
CORS_ALLOW_ORIGINS='["domain1", "domain2"]'
CORS_ALLOW_HEADERS='["content-type","authorization"]'universer:
config:
server:
http:
corsAllowOrigins: [domain1, domain2]
corsAllowHeaders: [content-type, authorization]Field notes:
DOCKER_NETWORK_SUBNET: Docker network CIDR.HOST_NGINX_PORT: external API port; change if it conflicts.HOST_MINIO_PORT/HOST_GRAFANA_PORTonly apply to built-in components.- K8s does not need these port mappings.
CORS_ALLOW_ORIGINS/CORS_ALLOW_HEADERScontrol allowed origins and headers.
K8s Production Required
The Helm chart deploys a demo UI by default and uses its domain. For production, disable the demo and set your domain:
collaboration-demo:
enabled: false
universer:
ingress:
enabled: true
hosts:
- host: use-your-own-domain-here
paths:
- path: /universer-api/
pathType: PrefixConfiguration Example
This example enables USIP and event sync, uses self-managed RDS/object storage, and adjusts scaling:
USIP_ENABLED=true
USIP_URI_CREDENTIAL=https://usip-demo.univer.ai/usip/credential
USIP_URI_USERINFO=https://usip-demo.univer.ai/usip/userinfo
USIP_URI_ROLE=https://usip-demo.univer.ai/usip/role
USIP_URI_COLLABORATORS=https://usip-demo.univer.ai/usip/collaborators
USIP_URI_UNITEDITTIME=https://usip-demo.univer.ai/unit-edit-time
AUTH_PERMISSION_CUSTOMER_STRATEGIES=[ {"action": 3, "role": 2}, {"action": 6, "role": 2} ]
EVENT_SYNC=true
DISABLE_UNIVER_RDS=true
DATABASE_DRIVER=postgresql
DATABASE_HOST=univer-postgresql
DATABASE_PORT=5432
DATABASE_DBNAME=univer
DATABASE_USERNAME=universer-biz
DATABASE_PASSWORD=123456
DISABLE_UNIVER_S3=true
S3_USER=universer-biz
S3_PASSWORD=123456
S3_REGION=cn-sz
S3_PATH_STYLE=true
S3_ENDPOINT=univer-s3:9652
S3_ENDPOINT_PUBLIC=univer.ai:9653
S3_DEFAULT_BUCKET=univer
HOST_NGINX_PORT=8010
UNIVERSER_REPLICATION_CNT=4
COLLABORATION_SERVER_REPLICATION_CNT=5
COLLABORATION_SERVER_MEMORY_LIMIT=2048collaboration-demo:
enabled: false
postgresql:
enabled: false
minio:
enabled: false
collaboration-server:
replicaCount: 5
maxMemoryLimit: 2048
universer:
replicaCount: 4
ingress:
enabled: true
hosts:
- host: usip-demo.univer.ai
paths:
- path: /universer-api/
pathType: Prefix
config:
usip:
enabled: true
uri:
userinfo: 'https://usip-demo.univer.ai/usip/userinfo'
collaborators: 'https://usip-demo.univer.ai/usip/collaborators'
role: 'https://usip-demo.univer.ai/usip/role'
credential: 'https://usip-demo.univer.ai/usip/credential'
unitEditTime: 'https://usip-demo.univer.ai/unit-edit-time'
auth:
permission:
customerStrategies: '[ {"action": 3, "role": 2}, {"action": 6, "role": 2} ]'
rabbitmq:
eventSync: true
database:
driver: postgresql
host: univer-postgresql
port: 5432
dbname: univer
username: universer-biz
password: 123456
s3:
accessKeyID: universer-biz
accessKeySecret: 123456
region: cn-sz
endpoint: 'univer-s3:9652'
endpointPublic: 'univer.ai:9653'
usePathStyle: true
defaultBucket: univer
temporal:
server:
config:
persistence:
default:
driver: sql
sql:
driver: postgres12
host: univer-postgresql
port: 5432
database: temporal
user: universer-temporal
password: 123456
visibility:
driver: sql
sql:
driver: postgres12
host: univer-postgresql
port: 5432
database: temporal_visibility
user: universer-temporal
password: 123456Deployment Steps
Docker Compose
- Prepare
.env.custom(optional) - Prepare
license.txtandlicenseKey.txt - If using self-managed RDS, download the DB init scripts and initialize
- Get Univer Server
- Online:
bash -c "$(curl -fsSL https://get.univer.ai/product)" [-- version] - Offline: download the All-in-one package and run
bash load-images.sh
- Online:
- Place
.env.customin the server root - Copy license files into
configs/ - Run
bash run.sh startin the server root - Run regression tests
K8s
-
Prepare
values.yaml(optional, version-controlled recommended) -
Prepare
license.txtandlicenseKey.txt -
If using self-managed RDS, run the init scripts
-
Install via Helm
- Online:
helm install -n univer --create-namespace \ -f your-own-values.yaml-path \ --set global.istioNamespace="univer" \ --set-file universer.license.licenseV2=your-license.txt-path \ --set-file universer.license.licenseKeyV2=your-licenseKey.txt-path \ univer-stack \ oci://univer-acr-registry.cn-shenzhen.cr.aliyuncs.com/helm-charts/univer-stack \ --version target-version kubectl rollout restart -n univer deployment/collaboration-server kubectl rollout restart -n univer deployment/universer- Offline:
export REGISTER=XXXX export NAMESPACE=XXX docker login $REGISTER bash load-image.sh --registry $REGISTER --namespace $NAMESPACE helm install -n univer --create-namespace \ -f your-own-values.yaml-path \ --set global.istioNamespace="univer" \ --set-file universer.license.licenseV2=your-license.txt-path \ --set-file universer.license.licenseKeyV2=your-licenseKey.txt-path \ univer-stack-xxxx.tgz
Parameter notes:
-n univer: namespace (keep consistent for upgrades).univer-stack: Helm release name (keep consistent for upgrades).--version target-version: target version; omit for latest.-f your-own-values.yaml-path: path to your custom values file.--set-file universer.license.licenseV2: path tolicense.txt.--set-file universer.license.licenseKeyV2: path tolicenseKey.txt.
To install built-in observability:
helm upgrade --install -n univer-observability --create-namespace \
--set global.univerNamespace="univer" \
univer-observability \
oci://univer-acr-registry.cn-shenzhen.cr.aliyuncs.com/helm-charts/univer-observabilityHow is this guide?
