Zum Hauptinhalt springen

Docker-Compose-Bereitstellung

Diese Seite beschreibt die Bereitstellung von FoxPlan auf einem einzelnen Server mit Docker Compose, hinter Traefik, das das Routing und die TLS-Terminierung übernimmt.

Für eine Produktionsumgebung mit Skalierung und Hochverfügbarkeit siehe Kubernetes-Bereitstellung.

Zwei Varianten​

Lokaler TestUnternehmensinstallation
FürFoxPlan auf einem Arbeitsplatzrechner evaluierenEinen gemeinsam genutzten Server, die Produktion
Dateieneine einzige docker-compose.ymldocker-compose.yml, install.sh und .env-Dateien
KonfigurationKeineDomain, TLS, SMTP, Secrets
Zugriffhttp://localhost:8090, nur vom Arbeitsplatzrechner aushttps:// Ihre Domain
E-MailsVon einem integrierten Testpostfach abgefangenÜber Ihren SMTP-Server versendet

Beide Varianten verwenden die gleichen Images und werden in zwei getrennten Schritten installiert:

  1. Images herunterladen: der einzige Zeitpunkt, zu dem ein Internetzugang nötig ist, vom Server selbst oder von einer Transfermaschine aus;
  2. Einrichtung — lokaler Test oder Unternehmensinstallation: Der Stack startet ausschließlich mit den bereits vorhandenen Images. Es wird keine Registry kontaktiert; fehlt ein Image, startet nichts.
Image-Registry von FoxPlan

Die Adresse der Registry wird Ihnen von FoxPlan mitgeteilt. Tragen Sie sie in die Variable FOXPLAN_REGISTRY ein, in der Datei .env neben der docker-compose.yml — für den Test ebenso wie für die Unternehmensinstallation. Der Lesezugriff ist frei: Es sind weder Zugangsdaten noch ein docker login erforderlich.

ImageRolle
foxplan_prodAnwendung
foxplan_prod-docsDiese Dokumentation
foxplan_prod-exportExport der Planungen (PDF, PNG, Excel, iCal)
foxplan_prod-mspImport und Export MS Project und Primavera

latest folgt der zuletzt veröffentlichten Version; jede Version trägt außerdem ein unveränderliches Tag, das in der Produktion fest angegeben werden sollte. Die Drittanbieter-Images (MongoDB, Redis, Traefik …) werden von Docker Hub bezogen, nicht aus dieser Registry.

Schritt 1: Images herunterladen​

Das Herunterladen der Images ist der einzige Zeitpunkt, zu dem der Stack Internetzugriff benötigt. Sobald die Images auf dem Server vorhanden sind, kontaktiert FoxPlan keine Registry mehr: weder beim Start noch im Betrieb noch beim Neustart eines Containers.

Herunterzuladende Images​

Die vier oben genannten FoxPlan-Images in der Version, die Sie bereitstellen, sowie die folgenden Drittanbieter-Images, alle von Docker Hub bezogen:

ImageLokaler TestUnternehmensinstallation
traefik:v3.6.4JaJa
mongo:8.2.7JaJa (MONGO_IMAGE)
redis:7.4JaJa (REDIS_IMAGE)
axllent/mailpit:v1.27JaNein: Ihre E-Mails gehen über Ihren SMTP-Server
dxflrs/garage:v2.1.0JaNur wenn Sie Garage hinzufügen — siehe Dateispeicher
curlimages/curl:8.14.1Ja: initialisiert Garage beim ersten StartNein
rclone/rclone:1.68.2NeinNur zum Sichern von Garage

Mit Ihrem eigenen S3-Speicher werden weder Garage noch curl noch rclone benötigt. Wenn Sie MONGO_IMAGE oder REDIS_IMAGE in der .env ändern, laden Sie das dort angegebene Image herunter.

Netzwerkflüsse​

Ausgehende Flüsse, die von der Docker-Engine während des Downloads ausgelöst werden, alle über HTTPS (TCP/443):

ZielRolleBetroffene Images
Host der FoxPlan-RegistryImage-Indexfoxplan_prod, -docs, -export, -msp
Token-Dienst der FoxPlan-RegistryAnonymes Zugriffstokendto.
Objektspeicher der FoxPlan-RegistryImage-Layer: Die Registry leitet die Downloads auf diesen Speicher umdto.
registry-1.docker.io, auth.docker.ioDocker Hub: Image-Index und ZugriffstokenDrittanbieter-Images
production.cloudflare.docker.com, production.cloudfront.docker.comLayer der Docker-Hub-Images (CDN)dto.
app.fox-plan.comDownload der Bereitstellungsdateien dieser SeiteLokaler Test (curl)

Filtert Ihre Firewall nach Image (Registry-Proxy, Positivliste in Harbor, Nexus, Artifactory …), erlauben Sie die oben genannten Namen und Versionen.

Die drei Hosts der FoxPlan-Registry werden Ihnen zusammen mit ihrer Adresse mitgeteilt. Eine Firewall, die nur die Domain der Registry zulässt, lässt den Index durch, blockiert aber die Layer: Der pull hängt dann oder bricht mit einer Zeitüberschreitung ab. Die Umleitungen zum Objektspeicher und zum CDN müssen ebenfalls erlaubt werden.

Die Betriebsflüsse — ACME-Zertifizierungsstelle, SMTP-Server, S3-Speicher, SSO-Anbieter — sind in der Netzwerkfluss-Matrix beschrieben.

Vom Server aus​

Wenn der Server die Registries für die Dauer des Downloads erreichen kann:

FOXPLAN_REGISTRY=<von FoxPlan mitgeteilte Adresse>
V=latest # in der Produktion: die in FOXPLAN_VERSION festgelegte Version
IMAGES=(traefik:v3.6.4 mongo:8.2.7 redis:7.4)
for i in foxplan_prod foxplan_prod-docs foxplan_prod-export foxplan_prod-msp; do
IMAGES+=("$FOXPLAN_REGISTRY/$i:$V")
done
# Lokaler Test: Testpostfach und integrierten Speicher hinzufügen.
# IMAGES+=(axllent/mailpit:v1.27 dxflrs/garage:v2.1.0 curlimages/curl:8.14.1)
# Unternehmensinstallation mit Garage:
# IMAGES+=(dxflrs/garage:v2.1.0 rclone/rclone:1.68.2)
for i in "${IMAGES[@]}"; do
case "$i" in
*-export:*|*-msp:*) docker pull --platform linux/amd64 "$i" ;;
*) docker pull "$i" ;;
esac || echo "ÉCHEC : $i"
done

Die Images foxplan_prod-export und foxplan_prod-msp gibt es nur für amd64: Die Schleife lädt sie immer für diese Architektur. Auf einem ARM-Server oder einem Mac mit Apple Silicon laufen sie in Emulation. Eine Zeile ÉCHEC meldet ein nicht heruntergeladenes Image: Beheben Sie das, bevor Sie zu Schritt 2 übergehen.

Die Zugänge zu den Registries können anschließend wieder geschlossen werden: Schritt 2 benötigt sie nicht mehr.

Aus einer unabhängigen Umgebung​

Sensibler Sektor: Images außerhalb Ihres Informationssystems abrufen

Gehört Ihre Organisation zu einem sensiblen Sektor — Verteidigung, Gesundheit, Energie, Finanzen, öffentliche Verwaltung, Betreiber kritischer Infrastrukturen —, öffnen Sie diese Flüsse weder vom Produktionsserver noch aus Ihrem internen Netz.

Rufen Sie die Images auf einer von Ihrem Informationssystem unabhängigen Maschine ab (dedizierter Rechner oder Transferumgebung, ohne Zugang zu Ihren internen Netzen), prüfen Sie sie und bringen Sie sie dann über Ihren üblichen Weg ein: interne Registry oder ein auf kontrolliertem Datenträger übertragenes Archiv.

# 1. Auf der Transfermaschine, außerhalb Ihres Informationssystems.
# Stellen Sie IMAGES genau wie oben zusammen (Test oder Unternehmen, mit oder ohne Garage):
# Ein hier vergessenes Image fehlt beim Start.
# --platform: die Architektur Ihres Zielservers (in den meisten Fällen amd64).
for i in "${IMAGES[@]}"; do docker pull --platform linux/amd64 "$i"; done
docker save -o foxplan-images.tar "${IMAGES[@]}"
sha256sum foxplan-images.tar > foxplan-images.tar.sha256

# 2. Prüfungen gemäß Ihrer Richtlinie: Virenschutz, Schwachstellenanalyse
# (Trivy, Grype…), Image-Fingerabdrücke (docker images --digests).

# 3. Auf dem Zielserver, ohne Internetzugang.
sha256sum -c foxplan-images.tar.sha256
docker load -i foxplan-images.tar

Die geladenen Images behalten ihren ursprünglichen Namen: Schritt 2 findet sie lokal. Wenn Sie eine interne Registry verwenden, taggen Sie sie dorthin um (docker tag, docker push), ziehen Sie sie vom Server aus und tragen Sie diese Registry in FOXPLAN_REGISTRY, MONGO_IMAGE und REDIS_IMAGE ein. Der Produktionsserver hat dann für seine Images keinerlei Fluss ins Internet mehr.

Schritt 2: Einrichtung​

Beide Varianten starten mit --pull never: Docker Compose verwendet die auf der Maschine vorhandenen Images und kontaktiert keine Registry. Fehlt eines, startet der Stack nicht und das fehlende Image wird benannt: Wiederholen Sie Schritt 1 mit der richtigen Liste und starten Sie dann erneut.

Ohne diese Option würde ein fehlendes Image stillschweigend heruntergeladen — oder der Start bliebe auf einem Server ohne Internetzugang an einer Zeitüberschreitung hängen.

Lokaler Test​

Auf einem Rechner mit Docker Desktop — oder mit Docker und Docker Compose v2 — genügt eine einzige Datei. Der einzige anzugebende Wert ist die Adresse der Registry, die Ihnen FoxPlan mitteilt:

mkdir foxplan-essai && cd foxplan-essai
curl -O https://app.fox-plan.com/docs/onpremise/essai/docker-compose.yml
echo "FOXPLAN_REGISTRY=<von FoxPlan mitgeteilte Adresse>" > .env

# Prüft, ob alle Images vorhanden sind, und startet dann, ohne etwas herunterzuladen.
MISSING=$(docker compose config --images | while read -r i; do
docker image inspect "$i" >/dev/null 2>&1 || echo "$i"
done)
if [ -n "$MISSING" ]; then echo "Fehlende Images, nichts wurde gestartet:"; echo "$MISSING"
else docker compose up -d --pull never; fi

Fehlt FOXPLAN_REGISTRY, bricht docker compose mit einer Meldung ab, die sie anfordert. Die vorherige Prüfung hält alles an, bevor auch nur ein Container erstellt wird; --pull never allein würde den Download ebenfalls verweigern, aber erst nachdem ein Teil des Stacks erstellt wurde.

Der Start dauert etwa eine Minute. Danach:

AdresseInhalt
http://localhost:8090FoxPlan — beim ersten Start die Diagnoseseite, dann das Anlegen Ihres Kontos
http://localhost:8090/mailpit/Die von FoxPlan gesendeten E-Mails, darunter der Registrierungscode
http://localhost:8090/docs/Diese Dokumentation

Keine E-Mail verlässt den Rechner: Ein Testpostfach, Mailpit, fängt sie alle ab.

Der Test enthält einen Dateispeicher, Garage: Projektdokumente, Anhänge von Kundenverträgen und Profilfotos funktionieren ohne Konfiguration. Auf einem Mac mit Apple Silicon laufen die beiden Export-Dienste in amd64-Emulation: Das ist so vorgesehen und etwas langsamer.

Mac mit Apple Silicon: MongoDB muss arm64 sein

Nur die beiden Exportdienste müssen als amd64 laufen. Wurden die übrigen Images als amd64 geladen oder heruntergeladen — etwa aus einem für einen amd64-Server vorbereiteten Archiv —, startet MongoDB nicht: Ab Version 5 benötigt es AVX-Befehle, die die Emulation nicht bereitstellt. Das Log von mongo zeigt dann „MongoDB 5.0+ requires a CPU with AVX support“, die Anwendung scheitert mit MongoTimeoutException und http://localhost:8090 antwortet mit 502.

Ein einfaches docker pull behebt das nicht: Das vorhandene amd64-Image gilt als aktuell. Fordern Sie ausdrücklich die arm64-Version an und starten Sie neu:

. ./.env # FOXPLAN_REGISTRY
for i in traefik:v3.6.4 mongo:8.2.7 redis:7.4 \
$FOXPLAN_REGISTRY/foxplan_prod:latest $FOXPLAN_REGISTRY/foxplan_prod-docs:latest; do
docker pull --platform linux/arm64 "$i"
done
docker compose up -d --pull never

Das erste Konto anlegen​

Der Test versendet keine echten E-Mails: Der Registrierungscode wird in Mailpit gelesen.

  1. Öffnen Sie http://localhost:8090: Eine Diagnoseseite öffnet sich (Bildschirm Ersteinrichtung) und zeigt Ihnen, ob alles korrekt funktioniert, ohne etwas abzufragen — siehe was geprüft wird.
  2. Wenn alles antwortet, klicken Sie auf Erste Instanz mit meinem Konto anlegen und geben Sie eine E-Mail-Adresse ein, deren Domain existiert — zum Beispiel Ihre geschäftliche Adresse; eine erfundene Domain wird abgelehnt. Die Nachricht bleibt in Mailpit: nichts verlässt den Rechner.
  3. Öffnen Sie in einem anderen Tab http://localhost:8090/mailpit/: Die Nachricht mit dem Registrierungscode trifft dort innerhalb weniger Sekunden ein.
  4. Kehren Sie zu FoxPlan zurück und geben Sie den Code ein. Der Assistent führt Sie anschließend durch Profil, Unternehmen, Module und Einladungen und lässt Sie zum Schluss Ihr Passwort wählen.

Dieses erste Konto ist Administrator der Instanz. Später versandte Einladungen und Passwort-Zurücksetzungen landen ebenfalls in Mailpit.

Der Test lauscht auf Port 8090 und nicht auf 80: macOS erlaubt einem Benutzer ohne Administratorrechte nicht, Port 80 nur auf der Adresse 127.0.0.1 zu öffnen, und Docker zeigt dann keinen Fehler an — die Seite antwortet einfach nicht.

Port 8090 ist bereits belegt? Wählen Sie beim Start einen anderen:

FOXPLAN_PORT=8091 docker compose up -d --pull never

und öffnen Sie dann http://localhost:8091.

Um alles zu stoppen: docker compose down (Daten bleiben erhalten) oder docker compose down -v, um bei null anzufangen.

Um den Test zu aktualisieren, wiederholen Sie Schritt 1 und führen dann erneut docker compose up -d --pull never aus. Ein docker compose up -d ohne Option lädt die neueste Version der FoxPlan-Images selbst herunter: praktisch auf einem verbundenen Rechner, aber Schritt 2 ist dann nicht mehr offline.

Die Testdaten hängen am Projektnamen foxplan-essai, nicht am Ordner: Ein neuer Ordner übernimmt den bestehenden Test mitsamt seinen Konten. Um bei null anzufangen, verwenden Sie docker compose down -v.

Nur zur Evaluierung

Der Test lauscht nur auf 127.0.0.1 — er ist nur vom Rechner selbst aus erreichbar — und verwendet allgemein bekannte Passwörter, die in der Datei stehen. Stellen Sie ihn nicht in einem Netzwerk bereit und erfassen Sie darin keine echten Daten: Um Benutzer aufzunehmen, wechseln Sie zur unten beschriebenen Unternehmensinstallation.

Unternehmensinstallation​

Herunterzuladende Dateien:

  • docker-compose.yml — der vollständige Stack. Diese Datei muss nicht geändert werden.
  • install.sh — bereitet die Installation vor: Zertifikat, Traefik-Konfiguration, Konsistenzprüfungen; prüft die Images und startet dann den Stack
  • env.example — die Infrastrukturvariablen, unter dem Namen .env zu speichern
  • env_application.example — die Variablen der Anwendung, unter dem Namen .env_application zu speichern
  • env_mongo.example und env_redis.example — die Zugangsdaten für MongoDB und Redis, unter den Namen .env_mongo und .env_redis zu speichern

Legen Sie die Dateien im selben Verzeichnis ab. Die gesamte Konfiguration erfolgt in den .env*-Dateien: Die docker-compose.yml bleibt unverändert, was Versionsupdates schmerzlos macht.

Der Rest dieser Seite betrifft die Unternehmensinstallation: Architektur, Voraussetzungen, Dimensionierung und dann das eigentliche Vorgehen der Installation.

Architektur​

Internet oder internes Netz
(DNS: DOMAIN → dieser Server)
│ 80 / 443
▼
┌─────────┐ TLS-Terminierung
│ Traefik │ Umleitung 80 → 443
└────┬────┘
┌──────────────────┼──────────────────┐
▼ ▼ ▼
/ (app:5000) /docs (docs:8080) /gantt-export:3200
product-server docs gantt-export
│ │
├──► mongo (internes Netz) └──► gantt-msp (intern)
└──► redis (internes Netz)
DienstRolleUnverzichtbar
product-serverFoxPlan-AnwendungJa
docsDiese Dokumentation, unter /docs bereitgestelltNein
gantt-exportExport der Planungen als PDF / PNG / Excel / iCalNein
gantt-mspImport und Export MS Project und PrimaveraNein
mongoDatenbankJa
redisCache und gemeinsame SitzungenJa
S3-SpeicherDateien: Dokumente, Anhänge, Profilfotos — Ihr eigener oder Garage, siehe DateispeicherNein

Ohne gantt-export funktioniert die Anwendung normal: Nur die grafischen Gantt-Exporte sind nicht verfügbar. Ohne S3-Speicher sind nur die Dateien nicht verfügbar.

Voraussetzungen​

  • Ein Linux-Server mit Docker und Docker Compose v2;
  • ein Domainname, dessen DNS-Eintrag auf diesen Server zeigt;
  • die Ports 80 und 443 für Ihre Benutzer erreichbar — keine weiteren, auch nicht für SSO: siehe Ports und SSO;
  • die in Schritt 1 heruntergeladenen Images: vom Server aus, mit vorübergehendem ausgehendem Zugriff auf die FoxPlan-Registry und Docker Hub, oder aus einer Transferumgebung importiert;
  • Ihre Lizenz;
  • für die Dateien ein S3-kompatibler Speicher — Ihr eigener oder Garage, siehe Dateispeicher;
  • je nach gewähltem TLS-Modus ausgehender Zugriff auf die Zertifizierungsstelle (siehe unten).

Ports und SSO​

Enterprise-SSO erfordert keinen zusätzlichen Port, ob es sich um die Standard-Anmeldung mit Microsoft oder Google oder um ein benutzerdefiniertes SSO handelt. Die Rückkehr einer Anmeldung wird vom Browser des Benutzers an https://IHRE_DOMAIN/login/oauth2/code/… übermittelt, über denselben Port 443 wie der Rest der Anwendung: Der Identitätsanbieter baut nie eine Verbindung zu Ihrem Server auf.

RichtungPortZwischenZweck
Eingehend443 (TCP)Arbeitsplätze der Benutzer → TraefikDie Anwendung und die SSO-Rückkehr
Eingehend80 (TCP)Arbeitsplätze der Benutzer → TraefikWeiterleitung auf 443; HTTP-01-Challenge im Modus acme
Ausgehend443 (TCP)Container product-server → IdentitätsanbieterToken-Austausch, Signaturschlüssel, Profil — Domains in der Netzwerkfluss-Matrix

Veröffentlichen Sie nicht den Port 5000 der Anwendung: Alles läuft über Traefik, das TLS terminiert. Lauscht Ihr Identitätsanbieter auf einem anderen Port als 443 — Keycloak auf 8443 zum Beispiel —, ist dieser Port ausgehend freizugeben, vom Server zum Anbieter.

Standard-SSO: Microsoft und Google​

Aktiviert über die Variablen TENANTID_AZURE, CLIENT_ID_AZURE, GOOGLE_CLIENT_ID… in .env_application. Die Weiterleitungs-URL wird aus BASE_URL gebildet:

https://IHRE_DOMAIN/login/oauth2/code/azure
https://IHRE_DOMAIN/login/oauth2/code/google

Tragen Sie sie unverändert in der bei Microsoft Entra oder Google registrierten Anwendung ein. Microsoft und Google akzeptieren nur https-URLs.

Benutzerdefiniertes SSO: in der Anwendung konfiguriert​

Okta, Keycloak, Microsoft, Google oder jeder OIDC-Anbieter, eingerichtet unter Unternehmens-SSO. Die Weiterleitungs-URL wird dann aus der eingehenden Anfrage rekonstruiert: Traefik übergibt der Anwendung die ursprüngliche Adresse — Header X-Forwarded-Proto, X-Forwarded-Host und X-Forwarded-Port — und FoxPlan leitet daraus https://IHRE_DOMAIN/login/oauth2/code/<Kennung> ab. Die Schaltfläche Konfiguration prüfen zeigt die genaue einzutragende URL.

Öffentlichen Port 443 beibehalten

Traefik ersetzt die X-Forwarded-*-Header durch das, was es selbst sieht. Erreichen die Benutzer FoxPlan über einen anderen öffentlichen Port (:8443) oder terminiert ein vor Traefik geschaltetes Gerät TLS und leitet HTTP weiter, berechnet FoxPlan eine andere Weiterleitungs-URL als die des Browsers: Der Anbieter lehnt die Anmeldung ab (redirect_uri mismatch). Ein vor Traefik geschalteter Load Balancer muss den Verkehr daher per HTTPS auf Port 443 oder als TCP ohne Entschlüsselung weiterleiten und dabei den ursprünglichen Domainnamen beibehalten.

Dimensionierung​

Die folgenden Werte liegen etwas über dem, was FoxPlan im Betrieb misst, um bei den Hauptkomponenten Reserven zu behalten.

Für den lokalen Test​

RessourceEmpfohlen
Prozessor4 vCPU
Docker zugewiesener Arbeitsspeicher8 GB (mindestens 6 GB)
Festplatte15 GB frei (inklusive Images)

Im Leerlauf belegt der Test etwa 1,5 GB, davon 1,1 GB für die Anwendung. In Docker Desktop wird der zugewiesene Arbeitsspeicher unter Settings → Resources eingestellt. Der Test legt keine Obergrenze pro Container fest: Er richtet sich nach dem Docker zugewiesenen Arbeitsspeicher.

Für die Unternehmensinstallation​

BenutzerProzessorArbeitsspeicherFestplatte (SSD)
Bis 1004 vCPU16 GB100 GB
100 bis 5008 vCPU32 GB, mit 2 Instanzen der Anwendung200 GB
DarüberSiehe Kubernetes-Bereitstellung

Die Festplatte umfasst die Datenbank, die in Garage gespeicherten Dateien, falls Garage auf demselben Server läuft, und Platz für lokale Sicherungen vor ihrer Übertragung. Um die Datenbank auf einer eigenen Festplatte abzulegen, siehe Volumes und Festplatten.

Speicherobergrenzen der Container​

Die docker-compose.yml der Unternehmensinstallation legt eine Speicherobergrenze pro Container fest. Ohne Obergrenze dimensionieren sich die JVM der Anwendung und MongoDB jeweils nach dem gesamten Arbeitsspeicher des Servers — ein Viertel für die JVM, die Hälfte für den MongoDB-Cache — und machen ihn sich schließlich gegenseitig streitig. Mit einer Obergrenze richtet sich jeder nach seiner eigenen.

ContainerStandardobergrenzeVariable (.env)
product-server (Anwendung)6 GBFOXPLAN_MEM_APP
mongo4 GBFOXPLAN_MEM_MONGO
gantt-export2 GBFOXPLAN_MEM_EXPORT
redis1 GBFOXPLAN_MEM_REDIS
gantt-msp512 MBFOXPLAN_MEM_MSP
docs256 MBFOXPLAN_MEM_DOCS
traefik256 MBFOXPLAN_MEM_TRAEFIK

Das ergibt 14 GB für einen Server mit 16 GB: Es bleibt genug für das Betriebssystem. Auf einem Server mit 32 GB verdoppeln Sie FOXPLAN_MEM_APP und FOXPLAN_MEM_MONGO. Die Anwendung nutzt 75 % ihrer Obergrenze für ihren Java-Heap; MongoDB reserviert automatisch die Hälfte seiner Obergrenze, abzüglich 1 GB, für seinen Cache.

Der Prozessor wird nicht begrenzt: Der Start der Anwendung und die Erzeugung der Exporte profitieren davon, und eine Obergrenze würde sie verlangsamen, ohne etwas zu schützen.

Mehrere Instanzen der Anwendung​

Für eine große Zahl von Benutzern starten Sie mehrere Instanzen der Anwendung auf demselben Server. Traefik erkennt sie und verteilt die Anfragen auf sie; die Sitzungen werden in Redis geteilt, sodass ein Benutzer von einer Instanz zur anderen wechseln kann, ohne abgemeldet zu werden. Geplante Aufgaben (Berichte, Routinen, Synchronisierungen) setzen eine Sperre in Redis: Nur eine Instanz führt sie aus, unabhängig von deren Anzahl.

In .env:

FOXPLAN_APP_REPLICAS=2

dann docker compose up -d. Jede Instanz hat ihre eigene Obergrenze FOXPLAN_MEM_APP: Der benötigte Arbeitsspeicher steigt entsprechend. Beispiel für einen Server mit 32 GB und 2 Instanzen:

FOXPLAN_APP_REPLICAS=2
FOXPLAN_MEM_APP=6g # pro Instanz: insgesamt 12 GB
FOXPLAN_MEM_MONGO=8g

Das ergibt mit den übrigen Containern insgesamt etwa 24 GB an Obergrenzen: Es bleibt Spielraum für das System.

Wann sollte man eine Instanz hinzufügen? Wenn der Prozessor des Anwendungscontainers zu Spitzenzeiten ausgelastet bleibt (docker stats), in der Praxis ab etwa hundert gleichzeitig aktiven Benutzern. Eine zweite Instanz hält den Dienst außerdem verfügbar, wenn eine davon neu startet.

Zwei Einschränkungen, die einem einzelnen Server eigen sind:

  • ein Versionsupdate erstellt alle Instanzen gleichzeitig neu: Die Unterbrechung bleibt kurz, aber sie besteht;
  • MongoDB bleibt ein einzelner Knoten auf demselben Server.

Ab 500 Benutzern oder für echte Hochverfügbarkeit (mehrere Server, schrittweise Bereitstellung ohne Unterbrechung) wechseln Sie zur Kubernetes-Bereitstellung.

Volumes und Festplatten​

Die MongoDB-Datenbank und die in Garage gespeicherten Dateien liegen in Verzeichnissen des Hosts, niemals in Docker-Volumes. Ein Docker-Volume verschwindet mit docker compose down -v oder einem docker system prune --volumes; ein Verzeichnis des Hosts nicht. Auf einer dedizierten Festplatte überdauert es zudem eine Neuinstallation von Docker oder des Systems.

DatenIm ContainerSpeicherort auf dem HostVariable (.env)
MongoDB-Datenbank/data/dbVerzeichnis, PflichtFOXPLAN_MONGO_DATA
Dateien in Garage (optional)/var/lib/garage/meta, /var/lib/garage/dataVerzeichnis, Unterordner meta und dataFOXPLAN_GARAGE_DATA
Redis (Cache, Sitzungen)/dataDocker-Volume redis_data oder ein VerzeichnisFOXPLAN_REDIS_DATA (optional)
Traefik-Zertifikate/certs, /acmeOrdner ./traefik/ der Installation—

Ohne FOXPLAN_MONGO_DATA verweigert docker compose den Start mit einer Meldung, die die Variable verlangt. Die env.example schlägt ./data/mongo im Installationsordner vor: Das genügt für ein Pilotprojekt. Legen Sie die Datenbank in der Produktion auf eine dedizierte Festplatte, wie unten beschrieben. Redis kann in einem Docker-Volume bleiben: Es enthält nur den Cache und die Sitzungen, die sich von selbst neu aufbauen (gehen sie verloren, werden lediglich die Benutzer abgemeldet).

Eine dedizierte Festplatte für die Datenbank einbinden​

Die folgenden Befehle werden auf dem Server als Administrator ausgeführt, vor dem ersten Start von FoxPlan. Sie setzen eine zusätzliche, leere Festplatte voraus, als SSD; ein Volume eines Speichersystems (SAN über iSCSI oder Fibre Channel) eignet sich ebenfalls. Vermeiden Sie NFS- oder SMB-Netzwerkfreigaben: MongoDB büßt dort an Leistung und Zuverlässigkeit ein.

1. Die Festplatte ermitteln. Sie erscheint ohne Partition und ohne Einhängepunkt, zum Beispiel sdb:

lsblk -f

2. Sie mit XFS formatieren, dem von MongoDB empfohlenen Dateisystem (Paket xfsprogs). Dieser Befehl löscht die Festplatte: Prüfen Sie ihren Namen zweimal.

sudo mkfs.xfs -L foxplan-data /dev/sdb

3. Sie dauerhaft einbinden, über ihre Kennung (UUID), die sich nicht ändert, wenn sich die Reihenfolge der Festplatten beim Neustart ändert:

sudo mkdir -p /srv/foxplan
UUID=$(sudo blkid -s UUID -o value /dev/sdb)
echo "UUID=$UUID /srv/foxplan xfs defaults,noatime 0 2" | sudo tee -a /etc/fstab
sudo mount -a
findmnt /srv/foxplan # muss /dev/sdb und xfs anzeigen

4. Docker warten lassen, bis die Festplatte eingebunden ist. Ohne diese Vorkehrung ist /srv/foxplan/mongo, wenn die Festplatte beim Serverstart nicht eingebunden ist, nur ein leerer Ordner auf der Systemfestplatte: MongoDB startet dort eine leere Datenbank, und FoxPlan scheint alles verloren zu haben.

sudo mkdir -p /etc/systemd/system/docker.service.d
printf '[Unit]\nRequiresMountsFor=/srv/foxplan\n' | sudo tee /etc/systemd/system/docker.service.d/foxplan-data.conf
sudo systemctl daemon-reload

Docker startet dann nicht mehr, solange /srv/foxplan nicht eingebunden ist.

5. Die Verzeichnisse in .env angeben:

FOXPLAN_MONGO_DATA=/srv/foxplan/mongo
FOXPLAN_GARAGE_DATA=/srv/foxplan/garage # nur wenn Sie Garage hinzufügen

6. Vorbereiten und starten. ./install.sh legt die Verzeichnisse an (mit meta und data für Garage); das MongoDB-Image setzt ihre Rechte selbst.

./install.sh
docker compose up -d

7. Prüfen, dass die Datenbank tatsächlich auf die dedizierte Festplatte schreibt:

docker inspect foxplan-mongo --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'
# /srv/foxplan/mongo -> /data/db
sudo ls /srv/foxplan/mongo # Dateien WiredTiger*, collection-*…
df -h /srv/foxplan # freier Platz auf der Datenfestplatte

Überwachen Sie anschließend den freien Speicherplatz: Fehlt MongoDB Platz, hält es abrupt an, und die Anwendung mit ihm.

Bestehende Installation: die Datenbank aus einem Docker-Volume herausholen​

Frühere Installationen legten die Datenbank im Docker-Volume mongo_data ab. Setzen Sie nach der Aktualisierung der docker-compose.yml FOXPLAN_MONGO_DATA und führen Sie dann ./install.sh aus: Findet das Skript das Verzeichnis leer vor, während im alten Volume eine Datenbank existiert, hält es an und zeigt den auszuführenden Kopierbefehl an. Im Detail:

docker compose stop product-server mongo
mkdir -p /srv/foxplan/mongo
docker run --rm -v IHR_PROJEKT_mongo_data:/from:ro -v /srv/foxplan/mongo:/to alpine \
sh -c 'cp -a /from/. /to/'
./install.sh
docker compose up -d

Den genauen Namen des Volumes liefert docker volume ls | grep mongo_data. Behalten Sie das alte Volume, bis Sie die Anwendung geprüft haben, und löschen Sie es dann (docker volume rm). Gleiches Prinzip für Garage: Kopieren Sie garage_meta nach $FOXPLAN_GARAGE_DATA/meta und garage_data nach $FOXPLAN_GARAGE_DATA/data.

Installation​

1. Vorbereitung​

chmod +x install.sh
./install.sh

Beim ersten Aufruf erstellt das Skript .env aus env.example und hält an, damit Sie die Datei ausfüllen können.

VariableBeschreibung
DOMAINBediente Domain, zum Beispiel foxplan.mein-unternehmen.de
FOXPLAN_REGISTRYAdresse der Image-Registry, von FoxPlan mitgeteilt
FOXPLAN_IMAGEBasisname der Images — bereits gesetzt: foxplan_prod
FOXPLAN_VERSIONBereitgestellte Version — legen Sie in der Produktion eine genaue Version fest
MONGO_IMAGE / REDIS_IMAGEValidierte Versionen der Abhängigkeiten
FOXPLAN_MONGO_DATAVerzeichnis der Datenbank auf dem Host — vorzugsweise eine dedizierte Festplatte, siehe Volumes und Festplatten
TLS_MODEselfsigned, acme oder static — siehe TLS
ACME_EMAIL, ACME_CA_SERVERZertifizierungsstelle, falls TLS_MODE=acme
latest in der Produktion

FOXPLAN_VERSION=latest folgt der zuletzt veröffentlichten Version: praktisch für einen Pilotbetrieb, danach nicht empfehlenswert. Ein einfacher Container-Neustart kann dann die Version wechseln, ohne dass Sie es entschieden haben. Legen Sie eine Version fest und führen Sie Versionsupdates bewusst durch.

Rufen Sie anschließend ./install.sh erneut auf: Es erzeugt das Standardzertifikat und die Traefik-Konfiguration passend zu Ihrem TLS_MODE. Das Skript ist idempotent — Sie können es beliebig oft ausführen, es ersetzt niemals ein vorhandenes Zertifikat oder eine vorhandene .env.

2. Anwendungsgeheimnisse​

Drei Dateien, aus den oben heruntergeladenen Vorlagen zu erstellen:

cp env_application.example .env_application # Datenbank, E-Mails, S3, JWT…
cp env_mongo.example .env_mongo # MongoDB-Zugangsdaten
cp env_redis.example .env_redis # Redis-Passwort

SECRET_KEY_JWT wird ein für alle Mal erzeugt:

openssl rand -base64 64 | tr -d '\n'

Zu beachtende Konsistenzen, sonst schlägt der Start fehl:

  • URL_MONGO verwendet Benutzer und Passwort aus .env_mongo, mit dem Host mongo;
  • SPRING_REDIS_PASSWORD ist identisch mit REDIS_PASSWORD;
  • BASE_URL lautet https:// gefolgt von DOMAIN — daraus werden die Links in den E-Mails gebildet.
Zwei Dateien, zwei Rollen

.env wird von Docker Compose gelesen, um den Stack zusammenzusetzen; sie wird nicht an den Anwendungscontainer übergeben. Die Variablen der Anwendung — Datenbank, E-Mails, S3, JWT — gehören in .env_application. Eine Anwendungsvariable in .env bleibt stillschweigend wirkungslos.

E-Mail-Versand​

FoxPlan versendet Einladungen, Passwort-Zurücksetzungen und Benachrichtigungen. Ohne Konfiguration wird keine E-Mail versendet: Die Anwendung startet und funktioniert, aber diese Funktionen bleiben stumm.

In .env_application einzutragen:

MAIL_PROXY_TYPE=smtp
MAIL_FROM=foxplan@mon-entreprise.fr

MAIL_SMTP_HOST=smtp.mon-entreprise.fr
MAIL_SMTP_PORT=587
MAIL_SMTP_USERNAME=
MAIL_SMTP_PASSWORD=
MAIL_SMTP_AUTH=true
MAIL_SMTP_STARTTLS=true
Versand aus der Anwendung prüfen

Die Registerkarte Einstellungen → Unternehmen → E-Mails zeigt für jede E-Mail, was Ihr Versandserver geantwortet hat, mit einem grünen / orangefarbenen / roten Banner zum Zustand der Plattform. Hier sollten Sie zuerst nachsehen, wenn ein Benutzer angibt, nichts erhalten zu haben — siehe E-Mail-Protokoll.

MAIL_PROXY_TYPE akzeptiert auch sendgrid oder gmail, für ein von Ihnen bereitgestelltes Konto; MAIL_PROXY_PARAMS enthält dann <Absender>;<Schlüssel oder Passwort>. In den allermeisten Installationen ist smtp die richtige Wahl.

Zwei Punkte entscheiden über das Schicksal Ihrer Nachrichten:

  • MAIL_FROM muss berechtigt sein, für Ihre Domain zu senden (SPF, DKIM). Ein nicht abgestimmter Absender landet im Spam oder wird abgewiesen, ohne dass die Anwendung davon erfährt.
  • BASE_URL bildet die Links in den E-Mails. Fehlt sie oder ist sie falsch, werden die Nachrichten korrekt versendet, aber die enthaltenen Links — Einladung, Zurücksetzung — führen ins Leere.

Zur Prüfung nach dem Start lösen Sie auf dem Anmeldebildschirm eine Passwort-Zurücksetzung aus und verfolgen die Logs:

docker compose logs -f product-server | grep -i mail

Ein fehlgeschlagener Versand erscheint dort als Warnung: Die Anwendung bricht wegen einer nicht versendeten E-Mail niemals ab.

3. Start​

./install.sh --start

Das Skript prüft zunächst, ob alle Images des Stacks auf dem Server vorhanden sind. Fehlt eines, nennt es dieses und bricht ab, ohne etwas zu starten: Laden Sie es herunter (Schritt 1) und starten Sie dann erneut. Andernfalls startet es den Stack mit docker compose up -d --pull never, ohne eine Registry zu kontaktieren.

Überprüfung:

docker compose ps
curl -I https://IHRE_DOMAIN/
curl -I https://IHRE_DOMAIN/docs/
Installation aus der Anwendung prüfen

Als Administrator angemeldet, testet die Registerkarte Einstellungen → FoxPlan-Konto → Diagnose die Datenbank, Redis, den E-Mail-Versand (eine Testnachricht wird an Sie gesendet), den S3-Speicher und den Exportdienst — siehe FoxPlan-Konto.

4. Das erste Konto anlegen​

Öffnen Sie https://IHRE_DOMAIN: Solange keine Instanz existiert, öffnet sich eine Diagnoseseite (Bildschirm Ersteinrichtung) und zeigt Ihnen, ob alles korrekt funktioniert — siehe was geprüft wird. Wenn alles antwortet, klicken Sie auf Erste Instanz mit meinem Konto anlegen. FoxPlan sendet einen Registrierungscode an die eingegebene Adresse, über den in .env_application konfigurierten SMTP-Server: Er landet im echten Posteingang des Empfängers. Geben Sie den Code ein, folgen Sie dem Assistenten (Profil, Unternehmen, Module, Einladungen) und wählen Sie dann Ihr Passwort.

Dieses erste Konto ist Administrator der Instanz: Verwenden Sie eine Adresse des Teams, das FoxPlan betreiben wird, kein vorübergehendes persönliches Konto.

E-Mail-Versand vor dem Anlegen des Kontos konfigurieren

Ohne funktionierenden SMTP-Server wird der Registrierungscode nicht versendet und es kann kein Konto angelegt werden. Deshalb bietet der Ersteinrichtungsbildschirm das Anlegen erst an, wenn der Versandserver erreicht wurde. Prüfen Sie den Abschnitt E-Mail-Versand und verfolgen Sie während der Registrierung die Logs: docker compose logs -f product-server | grep -i mail.

5. Installation prüfen: Diagnose​

Die Diagnose prüft, für den Test ebenso wie für die Unternehmensinstallation:

ElementPrüfung
AnwendungDer FoxPlan-Server antwortet
DatenbankMongoDB antwortet
CacheRedis antwortet
E-Mail-VersandserverDer SMTP-Server ist erreichbar (es wird keine E-Mail gesendet)
Speicher für AnhängeDer S3-Bucket ist zugänglich — „nicht konfiguriert“, wenn keiner vorhanden ist
Export der PlanungenDer Dienst gantt-export antwortet, aus Ihrem Browser getestet
DokumentationDer Dienst docs antwortet, aus Ihrem Browser getestet

Sie lässt sich auf zwei Arten öffnen:

  • Beim ersten Start, solange keine Instanz angelegt wurde: Der Bildschirm Ersteinrichtung startet sie von selbst und bietet dann an, die erste Instanz anzulegen, wenn kein Element fehlerhaft ist. Ein „nicht konfiguriertes“ Element, wie der Speicher im Test, blockiert nicht.
  • Danach jederzeit: über das Diagnose-Symbol rechts neben der Sprachauswahl auf der Anmeldeseite. Es erscheint nur bei einer On-Premise-Installation und funktioniert ohne Anmeldung — gerade dann, wenn sich niemand anmelden kann.

Ohne Anmeldung zeigt die Diagnose nur Status an, ohne Servernamen und ohne technische Meldung: Die Anmeldeseite ist für jeden sichtbar, der sie erreicht. Die Details zu jedem Fehler und eine echte Test-E-Mail finden Sie in der vollständigen Diagnose nach der Anmeldung: Einstellungen → FoxPlan-Konto → Diagnose — siehe FoxPlan-Konto.

Dateispeicher​

FoxPlan legt seine Dateien in einem S3-kompatiblen Objektspeicher ab: Projektdokumente, Anhänge von Kundenverträgen, Profilfotos. Die Dateien bleiben privat: Die Anwendung liest sie selbst zurück und liefert sie nach einer Rechteprüfung aus. Der Speicher muss daher weder aus dem Internet erreichbar sein noch öffentliche Berechtigungen verwalten.

Variable (.env_application)Rolle
FILE_S3_URLAdresse des S3-Dienstes, zum Beispiel http://garage:3900
FILE_S3_BUCKETBucket für die Dateien
FILE_S3_ACCESS_KEY, FILE_S3_SECRET_KEYZugangsdaten für den Bucket
FILE_S3_REGIONIn jeder Anfrage signierte Region — diejenige, die Ihr Speicher erwartet. Standard: fr-par

Ohne Speicher lassen Sie diese Variablen leer: Die Anwendung funktioniert, nur die Dateien sind nicht verfügbar. Eine Datei darf 10 MB nicht überschreiten. FoxPlan wendet On-Premise kein Kontingent an: Der Platz gehört Ihnen.

Ihr vorhandener Speicher​

Jeder S3-kompatible Speicher eignet sich: Ceph, NetApp StorageGRID, Scality, MinIO, AWS … Legen Sie einen Bucket und eigene Zugangsdaten für FoxPlan an, mit Lese-, Schreib- und Löschrechten auf diesen Bucket, und tragen Sie dann die obigen Variablen ein.

Sie haben bereits einen S3-Speicher: docker-compose.yml nicht ändern

Die Unternehmensinstallation enthält kein Garage: Sie bindet den Speicher an, den Sie angeben. Lassen Sie docker-compose.yml unverändert und legen Sie keine docker-compose.override.yml an; die gesamte Konfiguration gehört in .env_application:

# .env_application — Ihr S3-Speicher
FILE_S3_URL=https://s3.mein-unternehmen.local # Adresse des S3-Dienstes, ohne Bucket-Namen
FILE_S3_BUCKET=foxplan # für FoxPlan angelegter Bucket
FILE_S3_ACCESS_KEY=<Zugriffsschlüssel-ID>
FILE_S3_SECRET_KEY=<geheimer Schlüssel>
FILE_S3_REGION=<vom Speicher erwartete Region> # z. B. us-east-1, eu-west-3 oder die Ihres Ceph/MinIO

Wenden Sie die Konfiguration dann an: docker compose up -d product-server (der Container wird mit den neuen Werten neu erstellt).

  • Der Docker-Server muss die Adresse FILE_S3_URL erreichen (DNS, Firewall): Die Anwendung liest und schreibt die Dateien, nie der Browser.
  • Bei https muss das Zertifikat des Speichers von einer anerkannten Stelle signiert sein; ein nicht anerkanntes internes Zertifikat lässt die Verbindung scheitern.
  • Die Anwendung adressiert den Bucket im Pfad (https://s3.mein-unternehmen.local/foxplan/…): Ein DNS-Eintrag pro Bucket ist nicht nötig.
  • Prüfen Sie das Ergebnis in der Diagnose: Die Zeile Speicher muss grün sein.
Die Region muss die des Speichers sein

Die Region geht in die Signatur jeder Anfrage ein. Weicht FILE_S3_REGION von der vom Speicher erwarteten ab, werden alle Anfragen abgewiesen (Fehler 400 in den Logs). Die Diagnose meldet es: Speicher fehlerhaft.

Garage, falls Sie keinen haben​

Garage ist ein leichtgewichtiger Open-Source-S3-Speicher, der in Frankreich entwickelt wird. Der lokale Test enthält ihn bereits. Um ihn der Unternehmensinstallation hinzuzufügen, ohne die docker-compose.yml anzurühren, legen Sie daneben eine Datei docker-compose.override.yml an, die Docker Compose automatisch einliest:

services:
garage:
image: dxflrs/garage:v2.1.0
restart: always
volumes:
- ./garage.toml:/etc/garage.toml:ro
# Verzeichnisse des Hosts (FOXPLAN_GARAGE_DATA in .env): Sie überdauern „docker compose down -v“.
- ${FOXPLAN_GARAGE_DATA:?Setzen Sie FOXPLAN_GARAGE_DATA in .env}/meta:/var/lib/garage/meta
- ${FOXPLAN_GARAGE_DATA:?Setzen Sie FOXPLAN_GARAGE_DATA in .env}/data:/var/lib/garage/data
networks:
- foxplan-network
deploy:
resources:
limits:
memory: 1g

Geben Sie in .env das Verzeichnis der Dateien an, vorzugsweise auf der Datenfestplatte (siehe Volumes und Festplatten), und führen Sie dann ./install.sh erneut aus, das die Unterordner meta und data anlegt:

FOXPLAN_GARAGE_DATA=/srv/foxplan/garage

Legen Sie anschließend die Datei garage.toml an, mit zwei für Ihre Installation erzeugten Geheimnissen:

RPC_SECRET=$(openssl rand -hex 32)
ADMIN_TOKEN=$(openssl rand -base64 32)
printf '%s\n' \
'metadata_dir = "/var/lib/garage/meta"' \
'data_dir = "/var/lib/garage/data"' \
'db_engine = "sqlite"' \
'replication_factor = 1' \
'rpc_bind_addr = "[::]:3901"' \
'rpc_public_addr = "127.0.0.1:3901"' \
"rpc_secret = \"$RPC_SECRET\"" \
'' \
'[s3_api]' \
's3_region = "garage"' \
'api_bind_addr = "[::]:3900"' \
'' \
'[admin]' \
'api_bind_addr = "[::]:3903"' \
"admin_token = \"$ADMIN_TOKEN\"" > garage.toml
chmod 600 garage.toml

Starten Sie Garage und bereiten Sie es vor — Topologie, Bucket und Zugriffsschlüssel:

docker compose up -d garage
docker compose exec garage /garage status # Knoten-ID notieren (erste Spalte)
docker compose exec garage /garage layout assign -z dc1 -c 100G <KNOTEN_ID>
docker compose exec garage /garage layout apply --version 1
docker compose exec garage /garage bucket create foxplan
docker compose exec garage /garage key create foxplan # zeigt „Key ID“ und „Secret key“ an
docker compose exec garage /garage bucket allow --read --write --owner foxplan --key foxplan

-c 100G ist die angegebene Kapazität des Knotens: Passen Sie sie an den für Dateien reservierten Speicherplatz an. Übertragen Sie den Schlüssel schließlich in .env_application:

FILE_S3_URL=http://garage:3900
FILE_S3_BUCKET=foxplan
FILE_S3_ACCESS_KEY=<Key ID>
FILE_S3_SECRET_KEY=<Secret key>
FILE_S3_REGION=garage

und starten Sie dann die Anwendung neu:

docker compose up -d product-server

Die Diagnose (Symbol auf der Anmeldeseite oder FoxPlan-Konto → Diagnose) muss den Speicher als OK anzeigen. Garage ist nur aus dem internen Netz des Compose-Stacks erreichbar: Es wird kein Port veröffentlicht.

Ein Wert aus Ziffern gehört in Anführungszeichen

In einer YAML-Datei wird ein Wert, der nur aus Ziffern besteht, als Zahl gelesen: Aus einem Schlüssel 0000…01 würde 1. Wenn Sie einen Schlüssel in eine docker-compose-Datei schreiben, setzen Sie ihn in Anführungszeichen. In .env_application stellt sich das Problem nicht.

Eingehende und ausgehende Zertifikate​

FoxPlan verwendet zwei verschiedene Zertifikate, eines pro Verkehrsrichtung. Sie gehören weder an denselben Ort noch in dieselbe Komponente.

EINGEHENDE Flüsse Browser ──HTTPS──► Traefik ──► Anwendung
Traefik präsentiert das Zertifikat VON FOXPLAN → traefik/certs/

AUSGEHENDE Flüsse Anwendung ──HTTPS, SMTP TLS──► interne AD FS, SMTP, S3…
die Anwendung prüft DEREN Zertifikat anhand der → ca-sortant/
Zertifizierungsstelle, die sie signiert hat
Eingehende FlüsseAusgehende Flüsse
ZweckDen Browsern nachweisen, dass der Server wirklich DOMAIN istDer Anwendung erlauben, Ihre internen Dienste zu erkennen
Verwendet vonTraefikDer Anwendung (Java)
InhaltDas Zertifikat des FoxPlan-Servers und sein privater SchlüsselDas öffentliche Zertifikat der Zertifizierungsstelle — Stamm- und ggf. Zwischenzertifikate. Niemals ein privater Schlüssel
Ablageorttraefik/certs/tls.crt und traefik/certs/tls.keyca-sortant/<Name>.crt — eine oder mehrere Dateien; ein anderes Verzeichnis ist über FOXPLAN_OUTBOUND_CA_DIR in .env möglich
FormatPEM. tls.crt: Serverzertifikat, gefolgt von seinen Zwischenzertifikaten. tls.key: unverschlüsselter Schlüssel, ohne PassphrasePEM, Endung .crt zwingend. Eine Datei darf mehrere Zertifikate enthalten
PflichtJa — install.sh erzeugt ein selbstsigniertes, bis Ihres vorliegtNur wenn ein aufgerufener Dienst von einer nicht öffentlichen Stelle signiert ist
ÜbernahmeIm laufenden Betrieb, nach touch traefik/dynamic/tls.yml — siehe Modus staticBeim Start: ./install.sh (prüft die Dateien), dann docker compose restart product-server

Eine PEM-Datei ist eine Textdatei, die nur Blöcke -----BEGIN CERTIFICATE----- … -----END CERTIFICATE----- enthält (und für den Schlüssel -----BEGIN PRIVATE KEY-----).

Ist es dasselbe Zertifikat?​

Nein. Das eingehende Zertifikat ist das von FoxPlan, zusammen mit seinem privaten Schlüssel. Das ausgehende Zertifikat ist das der Zertifizierungsstelle, die die Zertifikate von AD FS, SMTP oder S3 signiert hat: öffentlich, ohne Schlüssel.

Häufig stammen sie jedoch aus derselben PKI. Hat die interne Stelle sowohl das Zertifikat von FoxPlan als auch das von AD FS signiert, dient das Zertifikat ihrer Stammzertifizierungsstelle:

  • in ca-sortant/, damit die Anwendung AD FS, SMTP … erkennt;
  • in traefik/ca/, nur im Modus acme mit interner PKI, damit Traefik den ACME-Server erkennt.

Kopieren Sie niemals tls.crt oder tls.key nach ca-sortant/: install.sh lehnt jede Datei mit privatem Schlüssel ab.

Ist eine ausgehende Zertifizierungsstelle nötig?​

Zeigen Sie vom FoxPlan-Server aus den Aussteller des Zertifikats jedes internen Dienstes an:

# AD FS, S3, APIs: HTTPS
echo | openssl s_client -connect adfs.mein-unternehmen.local:443 -servername adfs.mein-unternehmen.local 2>/dev/null \
| openssl x509 -noout -issuer
# SMTP mit STARTTLS
echo | openssl s_client -connect smtp.mein-unternehmen.local:587 -starttls smtp 2>/dev/null \
| openssl x509 -noout -issuer

Öffentlicher Aussteller (DigiCert, Sectigo, GlobalSign, Let's Encrypt…): nichts zu tun. Interner Aussteller: Fordern Sie beim PKI-Team das Zertifikat der Stammzertifizierungsstelle und ihrer Zwischenstellen im PEM-Format an und legen Sie es in ca-sortant/ ab.

Fehlt es, schlägt die SSO-Anmeldung bei der Rückkehr vom Anbieter fehl oder E-Mails werden nicht versendet, mit PKIX path building failed in docker compose logs product-server.

In PEM umwandeln​

Erhaltene DateiBefehl
Binäre .cer- oder .der-Dateiopenssl x509 -inform DER -in stelle.cer -out ca-sortant/stelle.crt
.p7b (Kette von Zertifizierungsstellen)openssl pkcs7 -print_certs -in kette.p7b | sed -n '/-----BEGIN/,/-----END/p' > ca-sortant/stelle.crt
.pfx oder .p12 (FoxPlan-Zertifikat mit Schlüssel)openssl pkcs12 -in foxplan.pfx -nokeys | sed -n '/-----BEGIN/,/-----END/p' > traefik/certs/tls.crt
openssl pkcs12 -in foxplan.pfx -nocerts -nodes | sed -n '/-----BEGIN/,/-----END/p' > traefik/certs/tls.key

Das sed entfernt die beschreibenden Zeilen (Bag Attributes, subject=…), die openssl hinzufügt. In ca-sortant/ würden sie den Start der Anwendung verhindern: install.sh erkennt sie und bricht mit dem Befehl zur Bereinigung ab. Prüfen Sie bei einer .pfx-Datei, dass tls.crt mit dem Serverzertifikat beginnt, vor den Zwischenzertifikaten.

TLS: drei Modi​

Unabhängig vom Modus installiert install.sh ein Standardzertifikat. Traefik liefert es ab dem ersten Start aus und liefert es weiterhin aus, falls der automatische Bezug fehlschlägt: Die Website fällt nie mangels Zertifikat aus, schlimmstenfalls erscheint eine Browserwarnung.

Die Wahl erfolgt über die Variable TLS_MODE und hängt an einer einzigen Frage: Wer stellt Ihre Zertifikate aus, und kann Traefik mit dieser Stelle kommunizieren?

TLS_MODEZertifizierungsstelleAusgehender ZugriffErneuerungVerwendung
selfsignedKeineKeiner—Evaluierung, Pilot
acmeLet's Encrypt oder Ihre PKIZur ZertifizierungsstelleAutomatischProduktion
staticBeliebigKeinerIhre AufgabeGeschlossenes Netz, Unternehmens-Wildcard

selfsigned — sofort starten​

Standardmodus. install.sh erzeugt ein selbstsigniertes Zertifikat für Ihre DOMAIN, gültig für 825 Tage. Die Anwendung ist sofort über HTTPS erreichbar, ohne öffentliches DNS und ohne Firewall-Freigabe.

Die Browser zeigen eine Warnung an: Das ist zu erwarten. Dieser Modus dient dazu, die Installation zu validieren, nicht dazu, den Dienst Benutzern bereitzustellen.

acme — automatische Erneuerung​

TLS_MODE=acme
ACME_EMAIL=admin@mon-entreprise.fr
ACME_CA_SERVER=https://acme-v02.api.letsencrypt.org/directory

Traefik bezieht das Zertifikat über eine HTTP-01-Challenge und erneuert es selbstständig. Mit Let's Encrypt gelten zwei Bedingungen: Die Domain muss öffentlich auf diesen Server auflösen, und Port 80 muss aus dem Internet erreichbar sein — die Challenge läuft darüber, auch wenn der gesamte Verkehr anschließend auf 443 umgeleitet wird.

Mit Ihrer internen PKI ändert sich nur die Adresse. Wenn Ihre Zertifizierungsstelle einen ACME-Dienst anbietet — step-ca, HashiCorp Vault, EJBCA —, ist das Protokoll dasselbe (RFC 8555):

ACME_CA_SERVER=https://ca.interne.mon-entreprise.fr/acme/acme/directory

Ist diese Zertifizierungsstelle privat, kennt Traefik sie nicht, und die Aushandlung würde an einem unbekannten Zertifikat scheitern. Stellen Sie ihm die Vertrauenskette bereit:

cp /pfad/zu/ca-interne.crt traefik/ca/ca-interne.crt

dann in .env:

LEGO_CA_CERTIFICATES=/ca/ca-interne.crt

install.sh prüft, ob die Datei vorhanden ist, und warnt Sie, falls sie fehlt.

In einem geschlossenen Netz ist dies der bevorzugte Modus: Sie behalten die automatische Erneuerung ohne jede Abhängigkeit vom Internet, sofern der Server Ihre Zertifizierungsstelle erreicht.

Kurzlebige Zertifikate

Interne PKIs stellen oft Zertifikate aus, die nur wenige Tage gültig sind. Das hat keine Folgen: Traefik erneuert entsprechend häufiger.

static — Ihr Zertifikat, Ihre Erneuerung​

Für ein offline bezogenes Zertifikat — Unternehmens-Wildcard, kommerzielles Zertifikat, interne Zertifizierungsstelle ohne ACME-Dienst. Es wird kein ACME konfiguriert: Traefik greift nie auf das Netz zu, um ein Zertifikat zu beziehen.

Überschreiben Sie einfach die erzeugten Dateien mit Ihren eigenen:

cp ihr-zertifikat.crt traefik/certs/tls.crt # vollständige Kette
cp ihr-schluessel.key traefik/certs/tls.key
chmod 600 traefik/certs/tls.key
Vollständige Kette

tls.crt muss das Serverzertifikat gefolgt von seinen Zwischenzertifikaten enthalten, in dieser Reihenfolge. Ein ohne Kette ausgeliefertes Zertifikat funktioniert in einem Desktop-Browser — der sie oft selbst ergänzt —, scheitert aber auf Mobilgeräten, in der Befehlszeile und bei API-Aufrufen. Ein klassisches und verwirrendes Symptom: „Bei mir funktioniert es.“

Die Erneuerung ist Ihre Aufgabe. Traefik überwacht die Zertifikatsdateien nicht, sondern nur seine Konfiguration in traefik/dynamic/: Berühren Sie nach dem Ersetzen beider Dateien diese Konfiguration, damit Traefik sie im laufenden Betrieb neu einliest, ohne Neustart und ohne Unterbrechung.

touch traefik/dynamic/tls.yml

Andernfalls liefert Traefik nach dem Ersetzen weiterhin das alte Zertifikat aus, bis zum nächsten Neustart — oder bis es abläuft. Legt Ihre PKI erneuerte Zertifikate in einem Verzeichnis ab, muss die Aufgabe, die sie nach traefik/certs/ kopiert, mit diesem touch enden.

Modus wechseln​

Ändern Sie TLS_MODE in .env, rufen Sie ./install.sh erneut auf und dann:

docker compose up -d traefik

Das vorhandene Zertifikat bleibt erhalten: Um mit einem neuen Zertifikat zu beginnen, löschen Sie traefik/certs/, bevor Sie das Skript erneut ausführen.

Ausgeliefertes Zertifikat prüfen​

echo | openssl s_client -connect IHRE_DOMAIN:443 -servername IHRE_DOMAIN 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates

Ist der Aussteller Ihre eigene Domain, obwohl Sie eine Zertifizierungsstelle erwartet haben, wird das Standardzertifikat ausgeliefert: Der Bezug ist fehlgeschlagen. Prüfen Sie der Reihe nach das DNS (dig +short IHRE_DOMAIN), die Portfreigaben und dann docker compose logs traefik | grep -iE 'acme|certificate|error'.

Laufender Betrieb​

docker compose ps # Zustand der Dienste
docker compose logs -f product-server # Logs der Anwendung
./install.sh --start # Versionsupdate, nach Schritt 1
docker compose down # Stopp, Daten bleiben erhalten

Die MongoDB-Datenbank und die Garage-Dateien liegen in Verzeichnissen des Hosts (FOXPLAN_MONGO_DATA, FOXPLAN_GARAGE_DATA, siehe Volumes und Festplatten): Sie überdauern Stopps, Versionsupdates und docker compose down -v. Auch ein Versionsupdate erfolgt in zwei Schritten: Laden Sie die Images der neuen Version herunter (Schritt 1), ändern Sie FOXPLAN_VERSION in .env und führen Sie dann erneut ./install.sh --start aus.

Sicherungen

Ein Datenverzeichnis ist keine Sicherung: Es schützt weder vor einem Festplattenausfall noch vor versehentlichem Löschen. Siehe Sicherung und Wiederherstellung.

Sicherung und Wiederherstellung​

Was gesichert werden muss​

ElementSichernWarum
MongoDBJaAlle Fachdaten: Projekte, Aufgaben, Budgets, Benutzer
.env*-DateienJaEnthalten Schlüssel, ohne die wiederhergestellte Daten unbrauchbar sind
RedisNeinCache und Sitzungen: baut sich selbst wieder auf, um den Preis einer erneuten Anmeldung der Benutzer
DateienJa, zeitgleich mit der DatenbankDokumente, Anhänge, Profilfotos: im S3-Speicher (FILE_S3_*), nicht in MongoDB. Mit Garage siehe In Garage gespeicherte Dateien
.env_application zusammen mit der Datenbank sichern

SECRET_KEY_JWT dient standardmäßig auch als Verschlüsselungsschlüssel für die in der Datenbank gespeicherten SSO-Geheimnisse. Wird ein MongoDB-Dump ohne diesen Schlüssel wiederhergestellt, sind die SSO-Konfigurationen nicht mehr entschlüsselbar: Sie sind vorhanden, aber unlesbar und müssen vollständig neu erfasst werden. Eine Datenbanksicherung ohne die Secrets ist keine vollständige Sicherung.

Sicherung​

Der Befehl wird im MongoDB-Container ausgeführt, der die nötigen Werkzeuge mitbringt:

docker compose exec -T mongo sh -c \
'mongodump --username "$MONGO_INITDB_ROOT_USERNAME" \
--password "$MONGO_INITDB_ROOT_PASSWORD" \
--authenticationDatabase admin --archive --gzip' \
> foxplan-$(date +%F-%H%M).archive.gz

Drei wichtige Details:

  • Die Zugangsdaten werden aus der Umgebung des Containers gelesen und nie auf der Befehlszeile geschrieben — sie erscheinen also weder in ps noch im Shell-Verlauf. Ein Passwort mit Leerzeichen oder Sonderzeichen funktioniert so ohne Maskierung.
  • --archive erzeugt eine einzige Datei statt einer Verzeichnisstruktur: einfacher zu übertragen, zu verschlüsseln und zu prüfen.
  • -T deaktiviert die Zuweisung eines Pseudo-Terminals, andernfalls würde der Binärstrom bei der Umleitung beschädigt.

Die Sicherung erfolgt im laufenden Betrieb, ohne die Anwendung anzuhalten.

Wiederherstellung​

docker compose exec -T mongo sh -c \
'mongorestore --username "$MONGO_INITDB_ROOT_USERNAME" \
--password "$MONGO_INITDB_ROOT_PASSWORD" \
--authenticationDatabase admin --archive --gzip --drop' \
< foxplan-2026-09-15-0300.archive.gz

--drop ersetzt jede Collection durch die gesicherte Version. Ohne diese Option würde die Wiederherstellung mit den vorhandenen Daten zusammengeführt, was zu einem inkonsistenten Zustand führt — selten das, was man will.

Redis-Cache nach jeder Wiederherstellung leeren

Redis hält Objekte vor, die auf Kennungen der alten Datenbank verweisen. Nach einer Wiederherstellung passen diese Verweise zu nichts mehr, und die Anwendung liefert verwirrende 500-Fehler — typischerweise ein Objekt „nicht gefunden“, obwohl es in der Datenbank vorhanden ist.

docker compose exec redis sh -c 'redis-cli -a "$REDIS_PASSWORD" FLUSHALL'
docker compose restart product-server

Das ist der häufigste vergessene Schritt nach einer Wiederherstellung.

In Garage gespeicherte Dateien​

MongoDB referenziert die Dateien über ihren Schlüssel: Sichern Sie die Datenbank und die Dateien zum selben Zeitpunkt, damit Sie sie gemeinsam wiederherstellen können.

Im laufenden Betrieb, Datei für Datei — die bevorzugte Methode. rclone kopiert den Inhalt des Buckets in ein Verzeichnis, ohne Garage anzuhalten; weitere Durchläufe übertragen nur Änderungen. Mit den Zugangsdaten aus .env_application:

NET=$(docker network ls --format '{{.Name}}' | grep foxplan-network)
KEY=$(grep ^FILE_S3_ACCESS_KEY= .env_application | cut -d= -f2)
SECRET=$(grep ^FILE_S3_SECRET_KEY= .env_application | cut -d= -f2)
mkdir -p /var/backups/foxplan/files
docker run --rm --network "$NET" -v /var/backups/foxplan/files:/backup \
rclone/rclone:1.68.2 sync :s3:foxplan /backup \
--s3-provider Other --s3-endpoint http://garage:3900 --s3-region garage \
--s3-access-key-id "$KEY" --s3-secret-access-key "$SECRET"

Zum Wiederherstellen vertauschen Sie Quelle und Ziel (sync /backup :s3:foxplan), auf einem wie bei der Installation vorbereiteten Garage.

Kalt, vollständiges Verzeichnis — für eine Servermigration. Halten Sie Garage an, archivieren Sie sein Verzeichnis (FOXPLAN_GARAGE_DATA, mit den Unterordnern meta und data) und starten Sie es dann neu:

docker compose stop garage
sudo tar czf garage-$(date +%F).tar.gz -C /srv/foxplan/garage meta data
docker compose start garage

Die beiden Unterordner gehören zusammen: Daten ohne ihre Metadaten sind unbrauchbar. Übertragen Sie die Sicherungen wie bei MongoDB vom Server weg.

Automatisieren​

Ein täglicher cron-Job mit Rotation über 30 Tage:

# /etc/cron.d/foxplan-backup
0 3 * * * root cd /opt/foxplan && \
docker compose exec -T mongo sh -c 'mongodump --username "$MONGO_INITDB_ROOT_USERNAME" --password "$MONGO_INITDB_ROOT_PASSWORD" --authenticationDatabase admin --archive --gzip' \
> /var/backups/foxplan/foxplan-$(date +\%F).archive.gz 2>/var/log/foxplan-backup.log && \
find /var/backups/foxplan -name 'foxplan-*.archive.gz' -mtime +30 -delete

Das % muss in einer cron-Tabelle als \% maskiert werden, sonst wird die Zeile an dieser Stelle abgeschnitten.

Eine lokale Sicherung schützt vor nichts

Ein Archiv, das auf dem gesicherten Server selbst liegt, verschwindet mit ihm. Übertragen Sie es auf einen separaten Speicher — Objektspeicher, Sicherungsserver, Band. Das ist die Hälfte der Arbeit, und genau die Hälfte, die vergessen wird.

Prüfen, ob die Sicherung verwendbar ist​

Eine nie wiederhergestellte Sicherung ist keine Sicherung, sondern eine Annahme. Testen Sie sie regelmäßig in einer separaten Umgebung — niemals in der Produktion.

# Schnelle Integritätsprüfung des Archivs, ohne etwas wiederherzustellen
gzip -t foxplan-2026-09-15-0300.archive.gz && echo "Archiv intakt"

Führen Sie dann ein- bis zweimal pro Jahr eine vollständige Wiederherstellung auf einem Testserver durch und melden Sie sich bei der Anwendung an: Das ist die einzige Prüfung, die etwas beweist.

Alternative: Kaltsicherung der Datenbank​

Für eine bitgenaue Kopie statt eines logischen Exports halten Sie die Datenbank an und archivieren ihr Verzeichnis (FOXPLAN_MONGO_DATA):

docker compose stop product-server mongo
sudo tar czf mongo-$(date +%F).tar.gz -C /srv/foxplan/mongo .
docker compose start mongo product-server

Schneller bei einer großen Datenbank, aber mit zwei Nachteilen: Die Anwendung ist während des Vorgangs unterbrochen, und das Archiv lässt sich nur auf einer identischen MongoDB-Version wiederherstellen. mongodump bleibt die bevorzugte Methode; behalten Sie diese hier Servermigrationen vor.

Wartungsmodus​

Eine aktivierbare Wartungsseite, die /docs während des Eingriffs erreichbar lässt, wird mit dem begleiteten Bereitstellungspaket geliefert. Sie ist nicht in den oben herunterladbaren Dateien enthalten. Wenden Sie sich an den Support, um sie zu erhalten.

Fehlerbehebung​

SymptomAnsatz
manifest unknown beim StartFOXPLAN_VERSION existiert nicht
pull access deniedFOXPLAN_REGISTRY oder FOXPLAN_IMAGE falsch — das Image heißt foxplan_prod — oder ein altes docker login wird von der Registry abgewiesen: docker logout <Host der Registry>
required variable FOXPLAN_REGISTRY is missingAdresse der Registry fehlt in .env: Tragen Sie den von FoxPlan mitgeteilten Wert ein
env file .env_mongo not foundDie Secrets-Dateien wurden nicht erstellt — siehe Anwendungsgeheimnisse
404 auf /DOMAIN falsch, oder Anwendung nicht gestartet — siehe Logs
502 / 503Die Anwendung startet noch oder wurde mit einem Fehler beendet
Ungültiges ZertifikatDNS, Ports, ACME-Logs — siehe Ausgeliefertes Zertifikat prüfen
Zweifel an einer IntegrationFoxPlan-Konto → Diagnose: Datenbank, Redis, E-Mails, S3 und Export mit einem Klick getestet
Keine E-Mail erhaltenMAIL_PROXY_TYPE oder MAIL_SMTP_* fehlen — siehe E-Mail-Versand
E-Mails erhalten, aber Links ungültigBASE_URL fehlt oder weicht von der öffentlichen Domain ab
E-Mails im SpamMAIL_FROM nicht berechtigt, für die Domain zu senden (SPF/DKIM)
Redis-AuthentifizierungsfehlerSPRING_REDIS_PASSWORD weicht von REDIS_PASSWORD ab
Mongo-AuthentifizierungsfehlerURL_MONGO inkonsistent mit .env_mongo
Gantt-Exporte schlagen fehlDienst gantt-export gestoppt oder MSP_SERVICE_ENDPOINT nicht erreichbar
500-Fehler „Objekt nicht gefunden“ nach einer WiederherstellungRedis-Cache nicht geleert — siehe Wiederherstellung
SSO-Konfiguration nach Wiederherstellung unlesbarSECRET_KEY_JWT unterscheidet sich von dem zum Zeitpunkt des Dumps verwendeten
Speicher in der Diagnose fehlerhaft, 400 bei S3-AnfragenFILE_S3_REGION weicht von der Region des Speichers ab (s3_region bei Garage) — siehe Dateispeicher
Speicher fehlerhaft direkt nach einem Wechsel des Garage-SchlüsselsDer Schlüssel in .env_application ist nicht der von Garage: docker compose exec garage /garage key info foxplan --show-secret
„Datei zu groß“Eine Datei überschreitet 10 MB
„Speicherkontingent erreicht“Nur SaaS: Die Instanz hat ihren Platz ausgeschöpft; eine Lizenz kann ihn erweitern
no matching manifest for linux/arm64Export-Images werden nur für amd64 veröffentlicht: platform: linux/amd64 bei gantt-export und gantt-msp beibehalten (Docker Desktop emuliert sie)
Die Anwendung startet in einer Schleife neu, Client id of registration 'google' must not be empty in den LogsImage älter als Oktober 2026 und SSO-Zeilen für Microsoft oder Google in .env_application deklariert, aber leer: Image aktualisieren oder diese Zeilen auskommentieren
FoxPlan startet nach einem Serverneustart ohne DatenDatenfestplatte nicht eingebunden: MongoDB ist auf dem leeren Ordner der Systemfestplatte gestartet. findmnt /srv/foxplan, dann Schritt 4 unter Volumes und Festplatten
required variable FOXPLAN_MONGO_DATA is missing a valueVariable fehlt in .env: siehe Volumes und Festplatten
MongoDB leer, nachdem FOXPLAN_MONGO_DATA gesetzt wurdeDaten im alten Docker-Volume verblieben: siehe Bestehende Installation
Die Anwendung startet in einer Schleife neu, https://${FILE_S3_URL} in den LogsFILE_S3_*-Variablen fehlen in .env_application: deklarieren Sie sie, auch leer — Anhänge sind dann nicht verfügbar
PKIX path building failed in den Protokollen der AnwendungInterne Zertifizierungsstelle von AD FS, SMTP oder S3 fehlt in ca-sortant/: siehe Eingehende und ausgehende Zertifikate