Integrating Collabora Online with OpenCloud on Debian 13: A Docker-Free Guide
In the previous post, I walked through installing OpenCloud 6.0 as a bare metal deployment inside a Debian 13 Incus container, compiled from source with libvips support for full image thumbnail generation. That gave us a fast, lean file sync and sharing platform with no Docker, no PHP, and no database.
This follow-up covers the next logical step: adding Collabora Online for in-browser document editing. Think Google Docs or Microsoft 365, but entirely self-hosted. Collabora Online is essentially LibreOffice running in the browser, which means format consistency between what you edit on your desktop in LibreOffice and what you edit online in OpenCloud. No surprises with document rendering or formatting differences.
Architecture overview
The setup consists of three Incus containers on the same host, all running Debian 13:
- opencloud (10.10.10.55) runs the OpenCloud binary with the built-in collaboration service
- collabora (10.10.10.56) runs Collabora Online Development Edition (CODE) via native Linux packages
- nginx-proxy (10.10.10.100) handles TLS termination for all domains
Three DNS records point to the server’s public IP:
cloud.yourdomain.comfor the OpenCloud web interfaceoffice.yourdomain.comfor the Collabora document editorwopi.yourdomain.comfor the WOPI protocol endpoint (how Collabora and OpenCloud exchange files)
OpenCloud communicates with Collabora through the WOPI (Web Application Open Platform Interface) protocol. When a user opens a document, OpenCloud generates a WOPI token and tells the browser to load Collabora in an iframe. Collabora then uses that token to fetch the file from OpenCloud via the WOPI endpoint, and saves changes back through the same channel.
Creating the Collabora container
On the Incus host, launch a new Debian 13 container with a static IP on the bridge:
incus launch images:debian/13 collabora -d eth0,ipv4.address=10.10.10.56
incus shell collaboraInstall prerequisites:
apt update && apt upgrade -y
apt install -y ca-certificates wget gnupgInstalling Collabora CODE packages
Collabora provides native Linux packages in .deb format, which means we can install it bare metal inside our Incus container without Docker. Import the signing key and add the repository:
mkdir -p /etc/apt/keyrings
wget -O /etc/apt/keyrings/collabora.gpg https://collaboraoffice.com/downloads/gpg/collaboraonline-release-keyring.gpg
cat > /etc/apt/sources.list.d/collabora.sources <<EOF
Types: deb
URIs: https://www.collaboraoffice.com/repos/CollaboraOnline/CODE-deb
Suites: ./
Signed-By: /etc/apt/keyrings/collabora.gpg
EOF
apt update
apt install coolwsd code-brandThe code-brand package pulls in the full Collabora Office engine (the LibreOffice core). This is a substantial download.
Configuring coolwsd
The coolwsd service is configured through /etc/coolwsd/coolwsd.xml. The coolconfig tool provides a convenient way to set values without editing XML directly.
Since TLS is terminated at the nginx-proxy, coolwsd runs without SSL internally. It needs to listen on all interfaces so the proxy can reach it, and it must know which WOPI host is allowed to make requests.
DOMAIN="yourdomain.com"
coolconfig set ssl.enable false
coolconfig set ssl.termination true
coolconfig set net.listen any
coolconfig set storage.wopi.host cloud.${DOMAIN}
coolconfig set server_name office.${DOMAIN}
coolconfig set net.frame_ancestors cloud.${DOMAIN}The net.frame_ancestors setting is critical. Without it, Collabora will refuse to load inside the OpenCloud iframe, and your browser will show a “connection refused” error when trying to open a document. This tells Collabora to include your OpenCloud domain in the frame-ancestors CSP header, allowing it to be embedded.
Then set the admin console password interactively:
coolconfig set-admin-passwordFixing the CA chain path
The default configuration references /etc/coolwsd/ca-chain.cert.pem, which does not exist after a fresh install. Coolwsd needs a valid CA bundle for outbound HTTPS connections to the WOPI endpoint. Symlink the system CA certificates:
ln -s /etc/ssl/certs/ca-certificates.crt /etc/coolwsd/ca-chain.cert.pemWithout this fix, coolwsd will crash on startup with File not found: /etc/coolwsd/ca-chain.cert.pem.
Start and verify
systemctl restart coolwsd
systemctl status coolwsd
curl -s http://127.0.0.1:9980/hosting/discovery | head -5The discovery endpoint should return XML listing all supported file types and their WOPI action URLs.
DNS and TLS certificates
Add A records for the two new subdomains, both pointing to the same public IP as your existing OpenCloud domain.
On the nginx-proxy container, issue ECC certificates with acme.sh:
acme.sh --issue -d office.yourdomain.com --webroot /var/www/acme
acme.sh --issue -d wopi.yourdomain.com --webroot /var/www/acmeAdjust the issuance method to match your existing workflow. Certificates are installed to /etc/acme/live/<domain>/fullchain.pem and /etc/acme/live/<domain>/privkey.pem.
Nginx proxy configuration
Two new server blocks on the nginx-proxy container. One proxies to Collabora, the other to the OpenCloud WOPI service. We define the domains and backend IPs as variables so you can adapt them to your environment.
office.yourdomain.com
This proxies to coolwsd on the Collabora container (port 9980). Note the WebSocket upgrade headers for the editing session, and the long read timeouts for documents that stay open.
OFFICE_DOMAIN="office.yourdomain.com"
COLLABORA_IP="10.10.10.56"
cat > /etc/nginx/sites-available/${OFFICE_DOMAIN} <<EOF
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ${OFFICE_DOMAIN};
ssl_certificate /etc/acme/live/${OFFICE_DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/acme/live/${OFFICE_DOMAIN}/privkey.pem;
location ^~ /browser {
proxy_pass http://${COLLABORA_IP}:9980;
proxy_set_header Host \$host;
}
location ^~ /hosting/discovery {
proxy_pass http://${COLLABORA_IP}:9980;
proxy_set_header Host \$host;
}
location ^~ /hosting/capabilities {
proxy_pass http://${COLLABORA_IP}:9980;
proxy_set_header Host \$host;
}
location ~ ^/cool/(.*)/ws\$ {
proxy_pass http://${COLLABORA_IP}:9980;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host \$host;
proxy_read_timeout 36000s;
}
location ~ ^/(c|l)ool {
proxy_pass http://${COLLABORA_IP}:9980;
proxy_set_header Host \$host;
}
location ^~ /cool/adminws {
proxy_pass http://${COLLABORA_IP}:9980;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host \$host;
proxy_read_timeout 36000s;
}
}
server {
listen 80;
listen [::]:80;
server_name ${OFFICE_DOMAIN};
return 301 https://\$host\$request_uri;
}
EOFwopi.yourdomain.com
This proxies to the OpenCloud collaboration service (port 9300) on the OpenCloud container.
WOPI_DOMAIN="wopi.yourdomain.com"
OPENCLOUD_IP="10.10.10.55"
cat > /etc/nginx/sites-available/${WOPI_DOMAIN} <<EOF
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name ${WOPI_DOMAIN};
ssl_certificate /etc/acme/live/${WOPI_DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/acme/live/${WOPI_DOMAIN}/privkey.pem;
location / {
proxy_pass http://${OPENCLOUD_IP}:9300;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
server {
listen 80;
listen [::]:80;
server_name ${WOPI_DOMAIN};
return 301 https://\$host\$request_uri;
}
EOFEnable and test
ln -s /etc/nginx/sites-available/${OFFICE_DOMAIN} /etc/nginx/sites-enabled/
ln -s /etc/nginx/sites-available/${WOPI_DOMAIN} /etc/nginx/sites-enabled/
nginx -t
systemctl reload nginxConfiguring OpenCloud for Collabora
This is where the integration happens. On the OpenCloud container, add the collaboration variables to the environment file.
Environment variables
Append the following to /var/lib/opencloud/config/config.env:
OC_ADD_RUN_SERVICES=collaboration
COLLABORATION_APP_NAME=CollaboraOnline
COLLABORATION_APP_PRODUCT=Collabora
COLLABORATION_APP_ADDR=https://office.yourdomain.com
COLLABORATION_APP_INSECURE=true
COLLABORATION_WOPI_SRC=https://wopi.yourdomain.com
COLLABORATION_GRPC_ADDR=0.0.0.0:9301
COLLABORATION_HTTP_ADDR=0.0.0.0:9300
PROXY_CSP_CONFIG_FILE_LOCATION=/var/lib/opencloud/config/csp.yamlThe OC_ADD_RUN_SERVICES=collaboration variable tells the main OpenCloud process to start the collaboration service internally. This is cleaner than running a separate systemd unit, as it shares the JWT secret, registry, and gateway configuration automatically.
Linking the config directory
The collaboration service looks for the OpenCloud configuration (including the auto-generated JWT secret) in /home/opencloud/.opencloud/config. If your config lives elsewhere, such as in /var/lib/opencloud/config/, create a symlink:
mkdir -p /home/opencloud/.opencloud
ln -s /var/lib/opencloud/config /home/opencloud/.opencloud/config
chown -h opencloud:opencloud /home/opencloud/.opencloud /home/opencloud/.opencloud/configWithout this, the collaboration service will fail to start with The jwt_secret has not been set properly in your config for collaboration.
Content Security Policy override
OpenCloud’s default CSP blocks the Collabora iframe. You need a full CSP override that adds office.yourdomain.com to the frame-src directive.
Create the CSP config at /var/lib/opencloud/config/csp.yaml:
directives:
child-src:
- "'self'"
connect-src:
- "'self'"
- "blob:"
- "https://raw.githubusercontent.com/opencloud-eu/awesome-apps/"
- "https://update.opencloud.eu/"
default-src:
- "'none'"
font-src:
- "'self'"
frame-ancestors:
- "'self'"
frame-src:
- "'self'"
- "blob:"
- "https://embed.diagrams.net/"
- "https://office.yourdomain.com"
img-src:
- "'self'"
- "data:"
- "blob:"
- "https://raw.githubusercontent.com/opencloud-eu/awesome-apps/"
manifest-src:
- "'self'"
media-src:
- "'self'"
object-src:
- "'self'"
- "blob:"
script-src:
- "'self'"
- "'unsafe-inline'"
style-src:
- "'self'"
- "'unsafe-inline'"This is the complete OpenCloud default CSP with one addition: https://office.yourdomain.com under frame-src. When you override CSP, you replace the entire default, so every directive must be included.
Set ownership and restart:
chown opencloud:opencloud /var/lib/opencloud/config/csp.yaml
systemctl restart opencloudThe /etc/hosts entries
This is the part that catches everyone off guard. In an Incus setup where containers communicate through a shared bridge, the containers need to resolve each other’s public domain names to internal IPs. Without these entries, the WOPI handshake fails because containers try to reach public domains that route externally instead of staying on the internal bridge.
On the OpenCloud container (10.10.10.55)
10.10.10.100 cloud.yourdomain.com
10.10.10.100 office.yourdomain.com
10.10.10.100 wopi.yourdomain.comThe cloud.yourdomain.com entry was already needed for the OIDC loopback fix from the initial OpenCloud installation. The other two ensure the collaboration service can reach both the Collabora discovery endpoint and the WOPI service through the nginx-proxy for proper TLS termination.
On the Collabora container (10.10.10.56)
10.10.10.100 wopi.yourdomain.com
10.10.10.100 cloud.yourdomain.com*Collabora needs to reach the WOPI endpoint to fetch and save documents, and the OpenCloud domain for WOPI token validation. Both go through the nginx-proxy.
Verification
Test each component individually before trying the full flow.
Collabora discovery endpoint
curl -I https://office.yourdomain.com/hosting/discoveryShould return HTTP 200 with Content-Type: text/xml.
WOPI endpoint
curl -I https://wopi.yourdomain.com/wopi/
Should return HTTP 405 with Allow: GET. The 405 is expected, it means the service is running but only accepts GET requests on that path.
OpenCloud collaboration service
ss -tlnp | grep -E '9300|9301'Both ports should show the OpenCloud process listening.
Full integration test
Open https://cloud.yourdomain.com in your browser, upload a .docx or .odt file, and click to open it. Collabora should load inside the browser with the full editing interface.
Troubleshooting
Here are the issues I encountered during this deployment and their solutions.
coolwsd crashes with “File not found: ca-chain.cert.pem”
Symlink the system CA bundle:
ln -s /etc/ssl/certs/ca-certificates.crt /etc/coolwsd/ca-chain.cert.pem“The jwt_secret has not been set properly”
The collaboration service can’t find the OpenCloud config. Ensure the symlink exists:
mkdir -p /home/opencloud/.opencloud
ln -s /var/lib/opencloud/config /home/opencloud/.opencloud/config
chown -h opencloud:opencloud /home/opencloud/.opencloud /home/opencloud/.opencloud/config“app ‘CollaboraOnline’ not found”
The collaboration service needs to be included in the main OpenCloud process. Add OC_ADD_RUN_SERVICES=collaboration to your environment file and restart OpenCloud.
“WopiDiscovery: wopi app url failed with unexpected code”
OpenCloud can’t reach Collabora’s discovery endpoint. Check the /etc/hosts entries on the OpenCloud container, ensure office.yourdomain.com resolves to the nginx-proxy IP, and verify that coolwsd is running on the Collabora container.
CSP iframe errors or “Content blocked” in browser
Two things need to be set. On the OpenCloud side, the CSP config must include office.yourdomain.com in the frame-src directive. On the Collabora side, net.frame_ancestors must be set to cloud.yourdomain.com:
coolconfig set net.frame_ancestors cloud.yourdomain.com
systemctl restart coolwsdwWOPI endpoint returns 502
Check the nginx error log on the proxy. If it shows “Connection refused” to the Collabora or WOPI upstream, verify the relevant service is actually running and listening on the expected port.
What we’ve built
The complete stack now consists of three Incus containers running on a single VPS with Debian 13:
- OpenCloud 6.0 compiled from source with libvips (file sync, sharing, WOPI collaboration service)
- Collabora Online 25.04 via native CODE packages (in-browser document editing)
- Nginx with acme.sh ECC certificates and CrowdSec (TLS termination, reverse proxy, WAF)
No Docker anywhere. Every component runs as a native systemd service inside lightweight Incus containers, fully inspectable and independently manageable. This is infrastructure you actually own and understand, not a black box of Docker layers you cannot easily audit.
Total memory footprint for the Collabora container sits around 160MB during idle, scaling up when documents are open. OpenCloud with the collaboration service included uses around 200MB. For a complete, self-hosted alternative to Google Workspace document editing, that is remarkably lean.
This is part of an ongoing series documenting a complete self-hosted infrastructure stack on Debian 13 with Incus. Previous: OpenCloud 6.0 Bare Metal Installation. Next up: full-text search with Apache Tika as well as Calendar and Contacts Integration with Radicale .
