Plattform-Hinweise
plMail führt überall dieselben Container aus, deshalb geht es auf dieser Seite fast nur um die beiden Dinge, die sich zwischen einem Linux-Server, einem Mac, Windows über WSL2 und einem NAS tatsächlich unterscheiden: wem die Dateien hinter einem Bind-Mount gehören und wie viel Arbeitsspeicher der Docker-Host dem Stack wirklich zugesteht. Fang mit Docker Compose an; hier stehen nur die Abweichungen.
Images werden für linux/amd64 und linux/arm64 als ein einziges Manifest veröffentlicht, jede der
folgenden Plattformen führt plMail also nativ aus. Es gibt keinen QEMU-Schritt einzuplanen, weder
auf einem Mac mit Apple Silicon noch auf einem ARM-NAS.
Was der Stack vom Host verlangt
Die Standarddatei startet acht dauerhaft laufende Container — den Webserver, den IMAP-Supervisor,
vier Messenger-Worker, Postgres und den Mercure-Hub — dazu secrets-init, das sich wieder beendet.
Unter dem optionalen Profil push kommt ein neunter hinzu, ntfy.
Die Zahlen, die tatsächlich irgendwo festgeschrieben sind: PHPs memory_limit steht in
frankenphp/conf.d/10-app.ini auf 2G, jeder Messenger-Worker ist durch --memory-limit=256M
begrenzt und startet sich bei --time-limit=3600 neu, upload_max_filesize ist 25M und
post_max_size 60M. Postgres läuft mit shared_preload_libraries=pg_stat_statements und im
Übrigen mit dem, was die Vorgaben des Images ihm geben.
Der typische Fehlerfall ist ein Host, der Speicher überbucht und dann per OOM tötet. Ein Worker, der mitten im Handler getötet wird, hinterlässt seine Nachricht zur erneuten Zustellung — das ist verkraftbar; der Container, der beim Halten des Migrations-Advisory-Locks getötet wird, ist es nicht: Jeder andere Container wartet die fünf Minuten Lock-Timeout ab und verweigert danach die Migration.
Linux-Server
Der unkomplizierte Fall und der, für den die Standard-compose.yaml geschrieben ist: Jeder
dauerhafte Pfad ist ein benanntes Volume, das Docker anlegt und besitzt, es gibt also nichts zu
chownen und keine Eigentümerfrage zu beantworten.
Die Container laufen intern als root und können damit ohnehin alles unterhalb von var/ schreiben.
Der Entrypoint setzt keine ACLs: Früher tat er das, bei jedem Start, und genau das ließ einen Start
auf drehenden Platten Minuten dauern — siehe Fehlerbehebung.
Zwei Dinge ändern sich in dem Moment, in dem du ein benanntes Volume gegen einen Bind-Mount tauschst, was du tun wirst, wenn die Mail auf einer bestimmten Platte liegen soll:
- Postgres läuft als uid 70 in
postgres:18-alpine— 999 gehört zum Debian-Image. Es kann sein Datenverzeichnis nicht in einem root-eigenen Elternverzeichnis anlegen, dieses Elternverzeichnis muss also mit den richtigen Besitzrechten existieren, bevor der Container startet: Leg das Postgres-Unterverzeichnis an und übertrage es perchown -R 70:70vor dem erstenup, oder aus einem einmaligen Dienst, der zuerst läuft. - ACLs werden nicht gebraucht. ZFS, NFS und alles andere mit NFSv4-ACLs lehnen
setfaclmit "Operation not supported" ab. plMail ruft es beim Start nicht mehr auf, es gibt also nichts abzulehnen.
Der typische Fehlerfall ist ein Bind-Mount, dessen Elternverzeichnis Docker als root angelegt hat. Postgres beendet sich während der Initialisierung mit einem Rechtefehler, der einen Pfad nennt, und die Anwendungscontainer verbrauchen anschließend ihre 60 Datenbankversuche im Warten auf etwas, das nie hochkommen wird.
Windows, über WSL2
Docker Desktop mit dem WSL2-Backend führt dieselben Linux-Container aus, am Stack ändert sich also nichts. Zwei Details auf der Host-Seite schon.
Lass das Projekt innerhalb des WSL2-Dateisystems. Ein Pfad unterhalb von /home/... in der
Distribution liegt auf einem nativen Linux-Dateisystem; ein Pfad unterhalb von /mnt/c liegt auf
dem Windows-Dateisystem, erreicht über eine Übersetzungsschicht, mit anderer Leistung und anderer
Semantik für Dateisperren und Besitzrechte. Benannte Volumes umgehen die Frage vollständig, was ein
weiteres Argument dafür ist, die Volumes der Standarddatei unangetastet zu lassen.
Der Arbeitsspeicher gehört der WSL2-VM, nicht Windows. Die Werte aus "Was der Stack vom Host
verlangt" weiter oben stammen aus dem, was WSL2 sich nehmen darf; konfiguriert wird das in
.wslconfig und nicht in Docker. Ein Stack, der sich verhält, als wäre die Maschine viel kleiner,
als sie ist, verweist meist auf diese Datei.
Wenn du zusätzlich die Browser-Testsuite von Windows aus ausführen willst: Einem Lauf zuzusehen erfordert WSLg unter Windows 11 oder einen X-Server; ohne beides nimm die Variante mit Trace-Aufzeichnung und sieh dir die Aufnahme hinterher an. Das betrifft ausschließlich die Entwicklung — CONTRIBUTING behandelt es.
Der typische Fehlerfall ist, einen Windows-Pfad in die Container einzuhängen. Es sieht aus, als
funktioniere es, und dann weicht das darunterliegende Sperrverhalten von dem ab, was der Entrypoint
erwartet — dieselbe Klasse von Problem, die flock unter macOS als Mutex für die
Abhängigkeitsinstallation unbrauchbar gemacht hat.
macOS
Apple Silicon zieht das linux/arm64-Image und führt es nativ aus.
Der relevante Unterschied ist wiederum das Dateisystem hinter einem Bind-Mount. Docker Desktop teilt Host-Verzeichnisse über VirtioFS, und zwei Punkte dazu sind in diesem Repository dokumentiert, weil sie gemessen und nicht vermutet wurden:
setfaclscheitert auf einer VirtioFS-Freigabe. Der Entrypoint hat keinen ACL-Schritt mehr, für den das eine Rolle spielte.flockist beratend und schließt auf einem solchen Mount containerübergreifend nicht zuverlässig aus. Die Abhängigkeitsinstallation des Entwicklungs-Entrypoints benutzt deshalbmkdirals Mutex, weil das eine einzelne atomare Operation ist, die mitEEXISTfehlschlägt. Vorher wurde beobachtet, wie zwei Container einen durchflockgeschützten Block im Abstand von 17 ms betraten.
Keines von beidem betrifft das veröffentlichte Image, das vendor/ einbackt und benannte Volumes
verwendet — beides zählt, wenn du einen Quelltextbaum einhängst, und das ist das Entwicklungs-Setup.
Wie unter Windows ist der verfügbare Arbeitsspeicher der der VM, konfiguriert in Docker Desktop, und nicht der des Macs insgesamt.
Der typische Fehlerfall ist die Annahme, ein Bind-Mount verhalte sich wie eine lokale Platte. Beim Lesen und Schreiben tut er das; bei Sperren, ACLs und Besitzrechten nicht, und jeder dieser Punkte hat hier bereits etwas kaputtgemacht.
NAS-Geräte
Ein NAS ist genau die Art Maschine, für die plMail gedacht ist. Auf TrueNAS installierst du plMail aus dem App-Katalog. Überall sonst sind das die Entscheidungen, vor denen du stehen wirst.
Ports. Ein NAS, dessen eigene Verwaltungsoberfläche bereits 80 und 443 belegt, verlangt, dass
plMail umzieht. Setze HTTP_PORT und HTTPS_PORT (und HTTP3_PORT, falls du HTTP/3 direkt
ausliefern willst), oder mach es wie die TrueNAS-App: Gib dem Dienst php
SERVER_NAME: ":80", damit FrankenPHP schlichtes HTTP ausliefert, leg das auf einen hohen Port
und terminiere TLS davor. Siehe Hinter einem Reverse-Proxy.
Ein Verzeichnis statt sechs. Setz APP_STORAGE_DIR=var/data, leg APP_SECRETS_DIR und
APP_SECRETS_FILE in denselben Baum und binde auf jedem Dienst einen einzigen Host-Pfad auf
/app/var/data. Das Ergebnis ist ein einziges Verzeichnis mit den
Geheimnissen, Anhängen, Rohnachrichten, Uploads und dem Postgres-Cluster — eine Sache zum
Snapshotten, eine Sache zum Sichern, und jeder Dienst sieht nachweislich denselben
Verschlüsselungsschlüssel. Richte es auf ein Dataset statt auf ein einfaches Verzeichnis, wenn du
Snapshots deiner Mail willst.
Wieder Besitzrechte. Neben chown -R 70:70 auf das Postgres-Unterverzeichnis muss uid 70 auch
in das Datenverzeichnis selbst wechseln können:
chmod o+x /app/var/data || setfacl -m u:70:--x /app/var/data || true
Ein mit der TrueNAS-Voreinstellung "Apps" angelegtes Dataset ist 770 apps:apps, was uid 70 keine
Möglichkeit gibt, in das Verzeichnis zu wechseln, in dem ihre eigenen Daten liegen. Ein reines
Ausführungsrecht gewährt genau dieses Wechseln, ohne das Auflisten oder Lesen von irgendetwas zu
erlauben.
Interpolation. Manche NAS-App-Oberflächen nehmen YAML entgegen, ohne ${VAR} zu unterstützen.
Dort legst du jede Einstellung in einen x-config-Block und referenzierst sie über YAML-Anker.
Führt dein NAS echtes docker compose aus, sind die Standarddatei und eine .env daneben
einfacher.
Image-Tags zählen hier mehr, weil eine NAS-App-Seite meist einen "Redeploy"-Knopf und keine
sichtbare Version hat. Mit pull_policy: always holt sich ein Redeploy einen neueren Build des
jeweils gesetzten Tags. Wähle den Tag bewusst — siehe
Aktualisieren.
Android-Push ohne Google ist der optionale Container, dessen Start sich auf einem NAS am meisten
lohnt: docker compose --profile push up -d bringt ntfy hoch, dem die Endpunkt-URL gehört, mit der
ein UnifiedPush-Distributor auf dem Telefon spricht. Seine Basis-URL wird aus SERVER_NAME
abgeleitet und in jeden ausgegebenen Endpunkt eingebacken; NTFY_BASE_URL richtig zu setzen, bevor
du die Adresse an Telefone gibst, erspart dir also, später jedes Gerät neu zu registrieren.
Der typische Fehlerfall ist eine NAS-App-Oberfläche, die verbirgt, welche Container es gibt. Zu
prüfen ist scheduler. Eine Installation ohne ihn sieht vollkommen gesund aus und pollt still nie,
weckt nie eine zurückgestellte Konversation, gleicht nie einen Kalender ab und löst nie eine
Erinnerung aus.
Fallstricke
Nur auf einigen dieser Plattformen bekommst du benannte Volumes geschenkt. Die Standard-
compose.yaml verwendet sie, und ihre Blob-Verzeichnisse gehören nicht dazu — siehe
den Speicherabschnitt der Docker-Seite.
Auf einem NAS wirst du mit ziemlicher Sicherheit stattdessen Bind-Mounts verwenden, und dann sind
die Besitzrechte deine Sache.
Postgres läuft nicht in jedem Image unter derselben uid. In den Alpine-Images, die plMail
verwendet, ist es 70, in den Debian-Images 999. Ein aus einer Anleitung für das jeweils andere
übernommenes chown hinterlässt einen Cluster, der nicht schreiben kann.
Der Entrypoint setzt keine ACLs und darf nicht wieder damit anfangen. var/cache und var/log
gehören zum Image. Wer aus einem Container heraus irgendetwas an einer Image-Datei ändert — eine
ACL zählt —, bringt den Storage-Treiber dazu, die ganze Datei in die eigene Schicht des Containers
zu kopieren und auf die Platte zu schreiben. Zwei rekursive setfacl-Durchläufe beim Start
schrieben deshalb zweitausend Dateien synchron neu, in jedem Container des Stacks gleichzeitig:
etwa eine Sekunde auf einer SSD und über zwei Minuten auf einem Pool aus drehenden Platten. Das
Image macht beide Verzeichnisse stattdessen beim Bauen beschreibbar, wo es einmal bezahlt wird.
ARM ist ein erstklassiges Ziel und muss es bleiben. Beide Architekturen werden auf nativen Runnern gebaut und zu einem Manifest zusammengeführt; alles, was du einem selbst gebauten Image hinzufügst, muss also auch ein arm64-Artefakt auflösen.
Ein Stack, der monatelang gut läuft und dann bei einem ersten Abgleich stehen bleibt, hat ein Speicherproblem. Der erste Abgleich eines großen Postfachs ist die anstrengendste Phase, die plMail je erlebt: Er stellt Tausende Jobs in die Warteschlange, vier Worker arbeiten gleichzeitig daran, und jeder darf 256M belegen, bevor er sich neu startet.
This page is generated from docs/de/install/platforms.md. Edit it there — changes made here are overwritten on the next push to main.
plMail
Using plMail
- Accounts and aliases
- Account health
- Templates
- Filters
- Calendar
- Invitations and events from mail
- Reminders
- Connected calendars
- Sharing and booking
- Contacts
- Files and integrations
- Security
- Other clients
- Appearance
- Administration
Installing and running
- Docker Compose
- Platform notes
- Behind a reverse proxy
- Configuration reference
- Backup and restore
- Configuration backup
- Upgrading
- Demo mode
- Troubleshooting
Providers
How it works