15 KiB
Installationsanleitung: Euro-Office DocumentServer mit Nextcloud-Anbindung
Diese Anleitung beschreibt die Installation des Euro-Office DocumentServers (Docker-Variante) auf einem Debian-13-Server und die Anbindung an eine bestehende Nextcloud-Instanz über Apache2 als Reverse-Proxy.
Projekt: https://github.com/Euro-Office/DocumentServer Dokumentation: https://euro-office.github.io/documentation/
1. Parameter dieser Installation
Alle im Dokument verwendeten Platzhalter sind hier zentral aufgeführt. Für eine abweichende Installation genügt es, diese Tabelle anzupassen und die Werte in den Befehlen entsprechend zu übernehmen.
| Platzhalter | Beschreibung | Beispielwert (diese Installation) |
|---|---|---|
<DS_HOSTNAME> |
Hostname des Euro-Office DocumentServers | eo-ds.ndnaht.de |
<NC_HOSTNAME> |
Hostname der bestehenden Nextcloud-Instanz | cloud.ndnaht.de |
<IPV4_ADDRESS> |
Öffentliche IPv4-Adresse des Servers | 135.181.90.132 |
<IPV6_ADDRESS> |
Öffentliche IPv6-Adresse des Servers | 2a01:4f9:c014:1ba8::1 |
<INSTALL_DIR> |
Arbeitsverzeichnis für Docker-Compose-Stack | /opt/euro-office |
<VHOST_DIR> |
Verzeichnis der Apache-vHost-Konfigurationsdateien | /usr/local/apache2/conf/vhosts |
<INTERNAL_PORT> |
Lokaler Port, auf den der Container gemappt wird | 8080 |
<DOCKER_BRIDGE_NAME> |
Fester Name der Docker-Netzwerk-Bridge | br-euroffice |
<CERT_DIR> |
Verzeichnis mit TLS-Zertifikat (hier: dehydrated) | /var/lib/dehydrated/certs/<DS_HOSTNAME> |
<NC_DOCROOT> |
Docroot-Verzeichnis der Nextcloud-Installation | /var/www/cloud.ndnaht.de/htdocs |
<NC_LOGFILE> |
Pfad der Nextcloud-Logdatei | /var/www/cloud.ndnaht.de/logs/cloud.log |
<PHP_BINARY> |
Pfad zum PHP-CLI-Binary | /usr/local/php/bin/php |
<JWT_SECRET> |
Gemeinsames Secret zwischen DocumentServer und Nextcloud | wird generiert (siehe Schritt 3) |
2. Voraussetzungen
- Betriebssystem: Debian 13 (getestet), amd64 oder arm64
- Docker Engine (Community Edition) inkl. Compose-Plugin v2, installiert über das offizielle Docker-Repository:
deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian trixie stable - Mindestens 4 GB RAM (8 GB empfohlen für Mehrbenutzerbetrieb), 10 GB freier Plattenplatz
- Bestehende Nextcloud-Instanz (getestet mit Nextcloud 34), erreichbar über Apache2, mit SSH-/
occ-Zugriff - DNS-Eintrag für
<DS_HOSTNAME>, zeigt auf<IPV4_ADDRESS>(bzw.<IPV6_ADDRESS>) - Gültiges TLS-Zertifikat für
<DS_HOSTNAME>(hier über dehydrated verwaltet) - Apache2 bereits installiert und aktiv, mit folgenden Modulen aktiviert:
proxy,proxy_http,proxy_wstunnel,ssl,headers,rewrite - Firewall-Hinweis: Falls der Server eine eigene iptables/nftables-Firewall betreibt (Eigenbau-Skript, CSF, ufw o. ä.), die nicht automatisch mit Docker zusammenarbeitet, siehe Abschnitt 8 ("Firewall-Anpassung für Docker-Bridge-Netzwerke"). Ohne diesen Schritt ist der Container u. U. nicht einmal vom Host selbst erreichbar.
3. Arbeitsverzeichnis und Secrets anlegen
sudo mkdir -p <INSTALL_DIR>
cd <INSTALL_DIR>
JWT-Secret generieren und in .env ablegen (wird von Docker Compose automatisch eingelesen):
echo "EO_JWT_SECRET=$(openssl rand -hex 32)" | sudo tee .env
sudo chmod 600 .env
Diesen Wert später 1:1 in die Nextcloud-Konfiguration übernehmen (Schritt 10).
4. docker-compose.yaml
Der Container wird nicht direkt öffentlich exponiert, sondern nur auf 127.0.0.1
gebunden — Apache übernimmt als Reverse-Proxy die öffentliche Erreichbarkeit inkl. TLS.
Das Docker-Netzwerk bekommt einen festen Bridge-Namen, damit Firewall-Regeln, die
auf den Interface-Namen verweisen, auch nach einem Neuaufbau des Netzwerks gültig bleiben.
services:
euro-office:
image: ghcr.io/euro-office/documentserver:latest
container_name: euro-office-ds
restart: unless-stopped
environment:
- JWT_ENABLED=true
- JWT_SECRET=${EO_JWT_SECRET}
# EXAMPLE_ENABLED bewusst NICHT gesetzt (=false) -> kein offenes Beispiel-App in Produktion
ports:
- "127.0.0.1:<INTERNAL_PORT>:80"
volumes:
- eo_data:/var/lib/euro-office/documentserver
- eo_logs:/var/log/euro-office/documentserver
- eo_config:/etc/euro-office/documentserver
networks:
- euroffice
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost/healthcheck || exit 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
volumes:
eo_data:
eo_logs:
eo_config:
networks:
euroffice:
driver: bridge
driver_opts:
com.docker.network.bridge.name: <DOCKER_BRIDGE_NAME>
Datei anlegen:
cd <INSTALL_DIR>
sudo tee docker-compose.yaml > /dev/null << 'EOF'
# --- Inhalt wie oben, Platzhalter durch tatsächliche Werte ersetzt ---
EOF
Konfiguration prüfen, bevor gestartet wird (zeigt das aufgelöste JWT_SECRET):
sudo docker compose config
5. Container starten
cd <INSTALL_DIR>
sudo docker compose up -d
sudo docker compose ps
Erwartet: Status Up ... (healthy) nach ca. 60–90 Sekunden (erste Fontgenerierung dauert etwas).
Lokalen Health-Check testen:
curl -i --max-time 5 http://127.0.0.1:<INTERNAL_PORT>/healthcheck
Erwartet: HTTP/1.1 200 OK
Falls dieser Test hängt/fehlschlägt (Timeout oder Connection Reset), obwohl
docker compose psbereitshealthyzeigt: Das deutet auf ein Firewall-Problem hin — siehe Abschnitt 8.
6. Apache-Module prüfen/aktivieren
sudo a2enmod proxy proxy_http proxy_wstunnel ssl headers rewrite
sudo systemctl restart apache2
sudo systemctl status apache2 --no-pager
7. Apache-vHost anlegen
Datei <VHOST_DIR>/<DS_HOSTNAME>.conf:
<VirtualHost <IPV4_ADDRESS>:80 [<IPV6_ADDRESS>]:80>
ServerName <DS_HOSTNAME>
RewriteEngine on
RewriteCond %{HTTPS} !=on
RewriteRule (.*) https://%{SERVER_NAME}%{REQUEST_URI} [R=301,L]
CustomLog /var/log/apache2/<DS_HOSTNAME>-access.log combined
ErrorLog /var/log/apache2/<DS_HOSTNAME>-error.log
</VirtualHost>
<VirtualHost <IPV4_ADDRESS>:443 [<IPV6_ADDRESS>]:443>
ServerName <DS_HOSTNAME>
SSLEngine on
SSLCertificateFile <CERT_DIR>/fullchain.pem
SSLCertificateKeyFile <CERT_DIR>/privkey.pem
ProxyPreserveHost On
ProxyRequests Off
# WebSocket-Verbindungen (Co-Editing) an proxy_wstunnel weiterreichen
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule ^/(.*) ws://127.0.0.1:<INTERNAL_PORT>/$1 [P,L]
RewriteCond %{HTTP:Upgrade} !=websocket [NC]
RewriteRule ^/(.*) http://127.0.0.1:<INTERNAL_PORT>/$1 [P,L]
# Wichtig: DocumentServer muss wissen, dass er hinter TLS läuft,
# sonst generiert er interne Editor-/Cache-URLs mit http:// statt https://
# ("Mixed Content", Editor kann Dokumente nicht laden/herunterladen).
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Host "<DS_HOSTNAME>"
RequestHeader set X-Forwarded-Port "443"
ProxyPass / http://127.0.0.1:<INTERNAL_PORT>/ retry=0
ProxyPassReverse / http://127.0.0.1:<INTERNAL_PORT>/
# Große Uploads (Dokumente) erlauben
LimitRequestBody 0
CustomLog /var/log/apache2/<DS_HOSTNAME>-access.log combined
ErrorLog /var/log/apache2/<DS_HOSTNAME>-error.log
</VirtualHost>
Aktivieren und testen:
sudo apachectl configtest
sudo systemctl reload apache2
Erreichbarkeit von außen prüfen:
curl -i https://<DS_HOSTNAME>/healthcheck
Erwartet: HTTP/1.1 200 OK
8. Firewall-Anpassung für Docker-Bridge-Netzwerke
Nur relevant, wenn eine eigene Firewall-Lösung eingesetzt wird, die den Datenverkehr über
Docker-Bridge-Interfaces nicht automatisch zulässt (z. B. eigene iptables/nftables-Skripte
mit einer restriktiven OUTPUT/FORWARD-Policy).
Symptom
docker compose pszeigt den Container alshealthy(interner Check funktioniert)- Ein
curlvon außerhalb des Containers auf127.0.0.1:<INTERNAL_PORT>oder die Container-IP hängt oder liefertConnection reset by peer/Operation timed out pingauf die Container-IP funktioniert (ICMP ist oft separat erlaubt), TCP jedoch nicht
Ursache
Docker bindet Container-Ports über einen docker-proxy-Prozess bzw. NAT-Regeln, die
Datenverkehr über das jeweilige Docker-Bridge-Interface (br-...) leiten. Eine restriktive
Firewall mit interface-spezifischen ACCEPT-Regeln (z. B. nur für eth0) und einer
abschließenden DROP-Regel lässt diesen Bridge-Verkehr nicht automatisch durch.
Lösung
- Docker-Netzwerk mit festem Bridge-Namen betreiben (siehe Abschnitt 4,
com.docker.network.bridge.name: <DOCKER_BRIDGE_NAME>) — verhindert, dass sich der Interface-Name bei jedem Neuaufbau des Netzwerks ändert. - In der Firewall-Konfiguration eine explizite Ausnahme für dieses eine Interface
eintragen (kein Wildcard wie
br+, das würde auch fremde/zukünftige Bridges ungefiltert durchlassen). Je nach Firewall-Lösung z. B.:- Eigenbau-Skript mit
unprotected_ifs-Variable:unprotected_ifs="<DOCKER_BRIDGE_NAME>" - iptables direkt: gezielte
ACCEPT-Regeln für-i <DOCKER_BRIDGE_NAME>/-o <DOCKER_BRIDGE_NAME>in den ChainsINPUT,OUTPUTundFORWARD
- Eigenbau-Skript mit
- Firewall neu laden und Docker-Daemon danach neu starten, damit Docker seine eigenen
iptables-Chains (
DOCKER,DOCKER-FORWARD,DOCKER-USER, …) neu aufbaut:sudo systemctl restart <firewall-service> sudo systemctl restart docker cd <INSTALL_DIR> && sudo docker compose up -d - Startreihenfolge dauerhaft absichern, damit das Problem nach einem Server-Reboot nicht
erneut auftritt — Docker soll immer nach der Firewall starten:
sudo mkdir -p /etc/systemd/system/docker.service.d/ sudo tee /etc/systemd/system/docker.service.d/after-firewall.conf > /dev/null << 'EOF' [Unit] After=<firewall-service>.service Wants=<firewall-service>.service EOF sudo systemctl daemon-reload
Restrisiko
Ein manueller Neustart der Firewall während Docker bereits läuft stört bestehende,
schon aktive Container-Verbindungen in der Regel nicht. Wird jedoch danach ein neues
Docker-Netzwerk angelegt (z. B. docker compose down && up, oder ein neuer Compose-Stack),
kann die Regel-Einfügung fehlschlagen (Failed to Setup IP tables: ... No chain/target/match by that name), weil Dockers eigene Chains beim Firewall-Neuaufbau entfernt wurden. In diesem
Fall genügt es, den Docker-Daemon einmal neu zu starten und den Stack erneut zu starten:
sudo systemctl restart docker
cd <INSTALL_DIR> && sudo docker compose up -d
Verifikation nach einem vollständigen Server-Reboot
sudo reboot
# nach dem Reboot, neu verbinden:
systemctl status <firewall-service> --no-pager
systemctl status docker.service --no-pager
sudo docker compose -f <INSTALL_DIR>/docker-compose.yaml ps
curl -i --max-time 5 https://<DS_HOSTNAME>/healthcheck
ip a | grep -A2 <DOCKER_BRIDGE_NAME>
sudo docker network ls
9. Nextcloud-Connector-App installieren
Die Euro-Office-Integration ist im offiziellen Nextcloud App Store gelistet.
Über die Weboberfläche:
- Als Administrator einloggen
- Profilbild (oben rechts) → Apps
- Kategorie „Büro & Text" auswählen
- „Euro-Office" suchen → „Download and enable"
Alternativ über die Konsole (occ):
cd <NC_DOCROOT>
sudo -u www-data <PHP_BINARY> occ app:install eurooffice
app:install lädt die App aus dem Nextcloud App Store herunter und aktiviert sie in
einem Schritt. Falls die App bereits heruntergeladen, aber nur deaktiviert ist, genügt
stattdessen:
sudo -u www-data <PHP_BINARY> occ app:enable eurooffice
Kontrolle per SSH:
sudo -u www-data <PHP_BINARY> <NC_DOCROOT>/occ app:list | grep -i eurooffice
Erwartet: Eintrag unter Enabled, z. B. eurooffice: 11.0.1
10. Nextcloud konfigurieren (DocumentServer-URL + JWT-Secret)
Zuerst prüfen, welche Config-Keys die App verwendet:
cd <NC_DOCROOT>
sudo -u www-data <PHP_BINARY> occ config:list eurooffice
DocumentServer-URL setzen:
sudo -u www-data <PHP_BINARY> occ config:app:set eurooffice DocumentServerUrl --value="https://<DS_HOSTNAME>/"
JWT-Secret setzen (identischer Wert wie in <INSTALL_DIR>/.env, Variable EO_JWT_SECRET):
sudo grep EO_JWT_SECRET <INSTALL_DIR>/.env
sudo -u www-data <PHP_BINARY> occ config:app:set eurooffice jwt_secret --value="<JWT_SECRET>"
Kontrolle:
sudo -u www-data <PHP_BINARY> occ config:list eurooffice
11. Funktionstest
- In Nextcloud ein Office-Dokument (
.docx,.xlsx,.pptx) öffnen - Der Euro-Office-Editor sollte sich im Browser öffnen und das Dokument anzeigen
Falls die Meldung „Herunterladen ist fehlgeschlagen" erscheint:
Browser-Entwicklerkonsole (F12) prüfen. Eine Meldung wie
„Laden von gemischten aktiven Inhalten … wurde blockiert" (Mixed Content) weist darauf hin,
dass der DocumentServer interne URLs mit http:// statt https:// erzeugt. Prüfen, ob die
X-Forwarded-*-Header im vHost (Abschnitt 7) korrekt gesetzt sind, danach:
sudo apachectl configtest
sudo systemctl reload apache2
Browser-Hard-Reload (Strg+Shift+R) und Dokument erneut öffnen.
12. Wartung / Hinweise
- Volumes:
eo_data,eo_logs,eo_config(benannte Docker-Volumes) enthalten Konfiguration, Cache und Logs. Für Backups:docker run --rm -v <volume>:/data -v $(pwd):/backup alpine tar czf /backup/<volume>.tar.gz -C /data . - Updates:
docker compose pull && docker compose up -d - Logs im Container:
sudo docker compose exec euro-office find /var/log/euro-office -iname "*.log" - EXAMPLE_ENABLED: bewusst nicht gesetzt/aktiviert — die Beispiel-App hat keine Zugriffskontrolle und darf nicht öffentlich erreichbar sein.