Docker Compose for Nextcloud + Collabora + Traefik?

Does anyone have a robust working Docker compose solution for these together? I consider myself a reasonably advanced linux user/admin, but have had a horrible time trying to setup Collabora with Nextclod behind Traefik. I have replicated so many examples from others online, I have downgraded the collabora container, I have played with the WOPI host and network access hosts in XML config to open up access to everyone, out of desperation. And more. Even messing with TLS SNI.

It is literally the worst IT experience I have had in years. Awful - and with awful documentation. So awful that the past weeks of trying (in my spare time) has been so exhausting I don’t even have the energy just now to explain the hundreds of different angle I have tried to fix it

Collabora is resource hungry, but could do the job. Onlyoffice doesn’t seem dependable/trustworthy as an open access FOSS project at all, so we can’t use it. Collabora’s public access servers work nicely FYI, but even the Collabora builtin app fails.

I am so exhausted by it. And looking online, so many people are angry, annoyed and give up. It can’t be that hard, no?!.

Surely NC should be able to put together ROBUST integration docs. Frankly, NC without Collabora, is like a car with three or no wheels.

1 Like

I have such setup running 1,5y and survived 2 major updates and hardware move without issues, the setup is stable. maybe this is the least problematic advanced feature of NC… (works much more stable than talk+coturn).

as I don’t use Collabora often first start takes multiple seconds but then it works good on old i3 desktop…

The only big problem I experienced was Collabora outage for multiple weeks caused by Collabora until I discovered the solution in Collabora forums (Collabora+SNI). The problem was identified much earlier but nobody made the link between Collabora and Nextcloud forums until I decided to dig into the issue… I described the problem here in detail.

see my config below, there is nothing special, maybe little hard to fit all the parts together… maybe the most important hint - as you can see from the examples all configurations use fqdn so each system: the client must reach both Nextcloud and Collabora by FQDN (with TLS) and both system must reach each other using fqdn as well. This is requirement is based on how WOPI works - once the user start editing a document, the originating system - here Nextcloud forwards the client to includes a frame from the WOPI system with an URL pointing back to itself like https://collabora.mydomain.tld/open_the_dcument_N0_32342_on_my_Nextcloud (not really) which makes Collabora to fire it’s engine and connect to the Nextcloud and access the document 32342 on the Nextcloud (and auto-magically gain access to it). This communication systems might be an issue in some situation e.g. Fritzbox blocks access to local systems using public URLs - see “DNS Rebind Protection”, IPv6 could become an issue as well as modern system tend to prefer it (and it doesn’t work well for dynamic DNS pointing to self-hosted system)… I’m using local PiHole running as Docker container to point public URLs to local IPs…

traefik

  traefik:
    image: traefik:v2.5
    container_name: traefik
    restart: unless-stopped
    command:
      - "--api=true"
      - "--api.insecure=true"
      - "--api.dashboard=true"
      #- "--log.level=DEBUG"
      - "--log.filePath=/etc/traefik/traefik.log"
      - "--providers.docker.endpoint=unix:///var/run/docker.sock"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=traefik_proxy"
      - "--providers.file.filename=/etc/traefik/config/tls.yaml"
      - "--providers.file.watch=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web-secure.address=:443"
      # Let's Encrypt
      - "--certificatesresolvers.letsencryptresolver.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencryptresolver.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencryptresolver.acme.email=youremail@forle.tld"
      - "--certificatesresolvers.letsencryptresolver.acme.storage=/etc/traefik/acme.json"
      # Logging options, what to log
      - "--accesslog=true"
      - "--accesslog.format=json"
      - "--accessLog.filters.statusCodes=400-499"      
    volumes:
      - ./traefik:/etc/traefik
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
    - traefik_proxy

traefik dynamic config

tls.yaml is nothing special (some security related settings and headers):

tls:
  options:
    TLSv13:
      minVersion: VersionTLS13
      cipherSuites:
        - TLS_AES_128_GCM_SHA256
        - TLS_AES_256_GCM_SHA384
        - TLS_CHACHA20_POLY1305_SHA256
      curvePreferences:
        - CurveP521
        - CurveP384
      sniStrict: true
        
    default:
      minVersion: VersionTLS12
      cipherSuites:
      - TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
      - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
      - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
      - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
      curvePreferences:
        - CurveP521
        - CurveP384
      sniStrict: true

http:
  middlewares:
    secHeaders:
      headers:
        browserXssFilter: true
        contentTypeNosniff: true
        frameDeny: true
        sslRedirect: true
        #HSTS Configuration
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 15768000
    secHeaders2:
      headers:
        browserXssFilter: true
        contentTypeNosniff: true
        frameDeny: true
        customFrameOptionsValue: SAMEORIGIN
        referrerPolicy: same-origin
        sslRedirect: true
        #HSTS Configuration
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 15768000
        forceSTSHeader: true
        sslForceHost: true
        browserXssFilter: true
        customResponseHeaders:
          server: "" # removes "Server" header
          X-Powered-By: "" # Removes X-Powered-By
          X-Robots-Tag: "none,noarchive,nosnippet,notranslate,noimageindex"
          #https://securityheaders.com/ is camera+mic enough?
          Permissions-Policy:	camera=('self'), microphone=('self'), autoplay=('self'), payment=(), screen-wake-lock=('self'), geolocation=()
          Feature-Policy:	"camera 'self'; microphone 'self'; payment 'none'; screen-wake-lock 'self'; geolocation 'none'; usb 'none'; vr 'none';"

collabora

  nextcloud-collabora:
    image: collabora/code
    container_name: collabora
    restart: unless-stopped
    networks:
      - traefik_proxy
    ports:
      - 9980:9980
    expose:
      - "9980"      
    environment:
      #should work as "domain=cloud1\.nextcloud\.com|cloud2\.nextcloud\.com"
      - domain=${COLLABORA_DOMAINS}
      - 'dictionaries=en_US,de_DE'
      - VIRTUAL_PROTO=http
      - VIRTUAL_PORT=9980
      - VIRTUAL_HOST=${COLLABORA_FQDN}
      - "extra_params=--o:ssl.enable=false  --o:ssl.termination=true"
    cap_add:
      - MKNOD
    tty: true
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik_proxy"
      - "traefik.http.routers.collabora.rule=Host(`${COLLABORA_FQDN}`)"
      - "traefik.http.routers.collabora.entrypoints=web"
      - "traefik.http.middlewares.collabora-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.routers.collabora.middlewares=collabora-https-redirect"
      - "traefik.http.routers.collabora-secure.entrypoints=web-secure"
      - "traefik.http.routers.collabora-secure.rule=Host(`${COLLABORA_FQDN}`)"
      - "traefik.http.routers.collabora-secure.tls=true"
      - "traefik.http.routers.collabora-secure.tls.certresolver=letsencryptresolver"

I fill variable with .env file:

# fqdn of Collaboa conatainer
COLLABORA_FQDN=collabora.mydomain.tld
COLLABORA_DOMAINS=nc1.mydomain.tld|nc2.mydomain.tld # share one Collabora installation with two NC instances

you can check your Collabora instance itself works fine by visiting https://${COLLABORA_FQDN}/hosting/discovery in your browser/curl/whatever - XML describing service capabilities should appear…

nextcloud

I don’t remember something special - use working traefik example - I’m too lazy to pull out my personal data from my config :wink: just choose the right option and enter the host in Settings > Admin > Collabora Online… (https://nc2.mydomain.tld/settings/admin/richdocuments) :

image

1 Like

Thank you so much. I think I saw your SNI pains in another thread. I will review what you kindly shared and report back.

I am not sure yet what I am doing wrong, but it has been a nightmare. I hope that it is my stupidity, but many seem to go through pain.

Very best

1 Like

@wwe , can you please help me?

For days and day I have struggled and cannot make Nextcloud + Collabora work behind my reverse-proxy, that is Caddy.

Using only Docker for the containers and exposing ports 80 and 9980 on host I can use Nextcloud with Collabora and also can access administrative panel of Collabora on https://collabora.local.cites.aop/browser/dist/admin/admin.html

My docker-compose to start Nextcloud/Collabora stack:

version: '2'
services:

  nextcloud-db:
    image: postgres:14.2-alpine
    container_name: nextcloud-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: nextcloud
      POSTGRES_PASSWORD: password
    volumes:
      - nextcloud-db:/var/lib/postgresql/data
      - /etc/localtime:/etc/localtime:ro

  nextcloud-cache:
    image: redis:7.0.0-alpine
    container_name: nextcloud-cache
    restart: unless-stopped
    mem_limit: 2048m
    mem_reservation: 512m
    command: redis-server --requirepass password

  nextcloud-app:
    image: nextcloud:24.0.0-fpm-alpine
    container_name: nextcloud-app
    restart: unless-stopped
    depends_on:
      - nextcloud-db
      - nextcloud-cache
    environment:
      POSTGRES_DB: nextcloud
      POSTGRES_USER: nextcloud
      POSTGRES_PASSWORD: password
      POSTGRES_HOST: nextcloud-db
      NEXTCLOUD_ADMIN_USER: admin
      NEXTCLOUD_ADMIN_PASSWORD: password
      NEXTCLOUD_TRUSTED_DOMAINS: nextcloud.local.cites.aop
      REDIS_HOST: nextcloud-cache
      REDIS_HOST_PASSWORD: password
      SMTP_HOST: mail.local.cites.aop
      SMTP_SECURE: tls
      SMTP_PORT: 587
      SMTP_AUTHTYPE: LOGIN
      SMTP_NAME: mailer@local.cites.aop
      SMTP_PASSWORD: password
      MAIL_FROM_ADDRESS: no-reply
      MAIL_DOMAIN: local.cites.aop
    volumes:
      - nextcloud-app:/var/www/html

  nextcloud-web:
    image: nginx:1.21.6-alpine
    container_name: nextcloud-web
    restart: unless-stopped
    ports:
      - 80:80
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    volumes_from:
      - nextcloud-app

  nextcloud-collabora:
    image: collabora/code:21.11.4.2.1
    container_name: nextcloud-collabora
    restart: unless-stopped
    ports:
      - 9980:9980
    depends_on:
      - nextcloud-web
    environment:
      - username=admin
      - password=password
      - dictionaries=en_US es_ES pt_BR
      - extra_params=--o:ssl.enable=false
    volumes:
      - /etc/localtime:/etc/localtime:ro

volumes:
  nextcloud-app:
  nextcloud-db:

#networks:
#  default:
#    name: caddy_net
#    external: true

At the end of the docker-compose.yml, commented, is a external network that, after activated should allow reverse-proxy (Caddy) to access this containers by name and I should comment the two ports that are exposed now, 80 and 9980.

The docker-compose.yml for Caddy:

version: "3.7"
services:

  caddy:
    image: caddy:2.5.1-alpine
    hostname: caddy
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./sites:/srv
      - app_data:/data
      - app_config:/config

volumes:
  app_data:
  app_config:

networks:
  default:
    name: caddy_net
    external: true

And the Caddyfile:

{
        debug
}

nextcloud.local.cites.aop {
        acme_server
        tls internal
        reverse_proxy nextcloud-web
}

collabora.local.setic.poa.br {
        acme_server
        tls internal
        reverse_proxy nextcloud-collabora:9980
}

I am testing everything at my internal network, also using a Pi-hole to manage the local DNS.

Searched a lot on Google, forums and cannot find the proper configuration between the three main actors: reverse-proxy, Nextcloud and Collabora.

:crazy_face:

I think you should better have started new thread as you issues has nothing to do with traefik. And you definitely should describe the issue…

The process of online edit documents in a browser called WOPI… search the web for you favorite explanation e.g. this one from Microsoft or this good drawing - don’t worry it’s about Sharepoint, the process is exactly the same. This official page shows some issues as well

a very basic drawing of the integration is here… followed by more complete example.

Most important requirement: each involved endpoint system needs to talk/reach others: client to both Collabora and Nextcloud, Collabora to Nextcloud and Nextcloud to Collabora. I’m not such deep into the mechanism but I could imagine you miss proper certificates as you stated you test everything internal and this could be an issue already - today integrations often require TLS which is almost impossible very hard with local setups… I would start with proper DNS/TLS setup (verify from each point - often container have another DNS view than the host or client) following with careful log reading…

Took an afternoon to get my own docker-compose.yml fully working. This is intended for a single server, running traefik, nextcloud, postgres and collabora in separate containers.

Some notes:

To make this run:

  • adapt domains cloud.example.com and office.example.com
  • adapt credentials USER and PASS for nextcloud, postgres and collabora
  • adapt hashed password for Traefik Dashboard, replace all $ with $$
  • adapt TZ=, NC_default_phone_region and dictionaries=
  • adapt email for Let’s Encrypt certificates
  • NextCloud emails can be enabled with SMTP variables on nextcloud
  • execute docker compose up -d --remove-orphans
  • In NextCloud menu Apps install Collabora Office, it turns into NextCloud Office
  • In NextCloud menu Settings->Office add link https://office.example.com

Let the fun begin:

# docker-compose.yml
version: '3.9'

services:
  traefik:
    image: traefik:v2.8.5
    container_name: traefik
    hostname: traefik
    restart: always
    environment:
      - TZ=Europe/Berlin
    ports:
      - 80:80
      - 443:443
      - 8080:8080
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /var/log:/var/log
      - /data/traefik:/data/traefik
    command:
      - --log.level=DEBUG
      - --accesslog=true
      - --accesslog.filepath=/var/log/traefik-access.log
      - --api.dashboard=true
      - --providers.docker=true
      - --providers.docker.exposedByDefault=false
      - --entryPoints.web.address=:80
      - --entrypoints.web.http.redirections.entryPoint.to=websecure
      - --entrypoints.web.http.redirections.entryPoint.scheme=https
      - --entryPoints.websecure.address=:443
      - --entryPoints.traefik.address=:8080
      - --certificatesresolvers.myresolver.acme.email=<EMAIL>
      - --certificatesresolvers.myresolver.acme.storage=/data/traefik/acme.json
      - --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.dashboard.entryPoints=traefik'
      - 'traefik.http.routers.dashboard.rule=PathPrefix(`/api`) || PathPrefix(`/dashboard`) || PathPrefix(`/debug`)'
      - 'traefik.http.routers.dashboard.service=api@internal'
      - 'traefik.http.routers.dashboard.middlewares=auth'
      - 'traefik.http.middlewares.auth.basicauth.users=<TR-USER>:<TR-HASHEDPASS>'

  postgres:
    image: postgres
    container_name: postgres
    restart: always
    volumes:
      - /data/postgres:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=nextcloud
      - POSTGRES_USER=<DB-USER>
      - POSTGRES_PASSWORD=<DB-PASS>

  nextcloud:
    image: nextcloud
    container_name: nextcloud
    hostname: nextcloud
    restart: always
    volumes:
      - /data/nextcloud:/var/www/html
    environment:
      - TZ=Europe/Berlin
      - POSTGRES_HOST=postgres
      - POSTGRES_DB=nextcloud
      - POSTGRES_USER=<DB-USER>
      - POSTGRES_PASSWORD=<DB-PASS>
      - NEXTCLOUD_ADMIN_USER=<NC-USER>
      - NEXTCLOUD_ADMIN_PASSWORD=<NC-PASS>
      - NEXTCLOUD_TRUSTED_DOMAINS=cloud.example.com
      - TRUSTED_PROXIES=traefik
      - OVERWRITEPROTOCOL=https
      - NC_default_phone_region=DE
    depends_on:
      - postgres
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.nextcloud.tls=true'
      - 'traefik.http.routers.nextcloud.tls.certresolver=myresolver'
      - 'traefik.http.routers.nextcloud.entrypoints=websecure'
      - 'traefik.http.routers.nextcloud.rule=Host(`cloud.example.com`)'
      - 'traefik.http.routers.nextcloud.middlewares=nextcloud-dav,nextcloud-header'
      - 'traefik.http.services.nextcloud.loadbalancer.server.port=80'
      - 'traefik.http.middlewares.nextcloud-dav.redirectRegex.regex=https://(.*)/.well-known/(card|cal)dav'
      - 'traefik.http.middlewares.nextcloud-dav.redirectRegex.replacement=https://$${1}/remote.php/dav/'
      - 'traefik.http.middlewares.nextcloud-dav.redirectRegex.permanent=true'
      - 'traefik.http.middlewares.nextcloud-header.headers.referrerPolicy=no-referrer'
      - 'traefik.http.middlewares.nextcloud-header.headers.stsSeconds=15552000'
      - 'traefik.http.middlewares.nextcloud-header.headers.forceSTSHeader=true'
      - 'traefik.http.middlewares.nextcloud-header.headers.stsPreload=true'
      - 'traefik.http.middlewares.nextcloud-header.headers.stsIncludeSubdomains=true'
      - 'traefik.http.middlewares.nextcloud-header.headers.browserXssFilter=true'
      - 'traefik.http.middlewares.nextcloud-header.headers.customRequestHeaders.X-Forwarded-Proto=https'

  collabora:
    image: collabora/code
    container_name: collabora
    hostname: collabora
    restart: always
    environment:
      - TZ=Europe/Berlin
      - aliasgroup1=https://cloud.example.com
      - dictionaries=en_US,de_DE
      - username=<CO-USER>
      - password=<CO-PASS>
      - extra_params=--o:ssl.enable=false --o:ssl.termination=true
    tty: true
    labels:
      - 'traefik.enable=true'
      - 'traefik.http.routers.collabora.tls=true'
      - 'traefik.http.routers.collabora.tls.certresolver=myresolver'
      - 'traefik.http.routers.collabora.entrypoints=websecure'
      - 'traefik.http.routers.collabora.rule=Host(`office.example.com`)'
      - 'traefik.http.routers.collabora.middlewares=collabora-header'
      - 'traefik.http.services.collabora.loadbalancer.server.port=9980'
      - 'traefik.http.middlewares.collabora-header.headers.referrerPolicy=no-referrer'
      - 'traefik.http.middlewares.collabora-header.headers.stsSeconds=15552000'
      - 'traefik.http.middlewares.collabora-header.headers.forceSTSHeader=true'
      - 'traefik.http.middlewares.collabora-header.headers.stsPreload=true'
      - 'traefik.http.middlewares.collabora-header.headers.stsIncludeSubdomains=true'
      - 'traefik.http.middlewares.collabora-header.headers.browserXssFilter=true'
      - 'traefik.http.middlewares.collabora-header.headers.customRequestHeaders.X-Forwarded-Proto=https'
2 Likes

Very helpful. Followed @bluepuma77’s instructions exactly and got a perfectly functioning NC instance, secure, with Office ready to go.

@bluepuma77 : Thank you very much.

I got a perfect working system with office after I tried other options for weeks