Plik (root-gg/plik) ist ein schlanker, selbst gehosteter Filesharing-Dienst à la WeTransfer — ideal, um schnell Dateien mit Kollegen, Freunden oder Kunden zu teilen, ohne Daten bei einem Drittanbieter abzulegen. In diesem Beitrag zeige ich zwei Probleme, auf die ich beim Betrieb per Docker Compose gestoßen bin, und wie ich sie gelöst habe:
- Uploads verschwanden nach jedem
docker compose pull && down && up
- Die Instanz wurde von Bots mit leeren Fake-Uploads zugemüllt
Ausgangslage: Docker-Compose-Setup
Mein ursprüngliches Compose-File sah so aus:
services:
plik:
image: rootgg/plik:latest
container_name: plik-app
ports:
- "3002:3000"
volumes:
- ./data/files:/home/plik/server/files
- ./data/plikd.cfg:/home/plik/server/plikd.cfg:ro
- ./data/background1.jpg:/home/plik/webapp/dist/img/background.jpg:ro
- ./data/settings.json:/home/plik/webapp/dist/settings.json:ro
environment:
- PLIK_DISABLE_AUTHENTICATION=false
- PLIK_DEBUG=true
- PLIK_DEFAULT_TTL=365d
restart: always
networks:
docknet:
ipv4_address: 172.16.0.28
networks:
docknet:
external: true
Auf den ersten Blick sauber: Die Datei-Blobs (./data/files) sind als Volume gemountet, ebenso die Konfiguration. Trotzdem waren nach jedem Update alle vorherigen Uploads weg.
Problem 1: Verschwundene Uploads nach Container-Neustart
Ursache: Plik trennt Dateiinhalte und Metadaten strikt. Die eigentlichen Datei-Blobs liegen im DataBackend (bei mir file, Verzeichnis files), aber welche Dateien überhaupt existieren, wird in einer separaten Datenbank verwaltet — im Standard-Setup eine SQLite-Datei:
[MetadataBackendConfig]
Driver = "sqlite3"
ConnectionString = "plik.db"
Dieser Pfad ist relativ zum Arbeitsverzeichnis des Servers (/home/plik/server) und lag bei mir nicht in einem Volume. Bei jedem docker compose down && up -d wurde also ein frischer Container erzeugt — inklusive einer leeren plik.db. Die physischen Dateien lagen zwar noch unverändert in ./data/files, aber ohne passenden Datenbankeintrag kannte Plik sie schlicht nicht mehr. Für mich als Nutzer sah das wie ein kompletter Datenverlust aus.
Lösung: Die Metadaten-Datenbank ebenfalls als Volume mounten:
volumes:
- ./data/files:/home/plik/server/files
- ./data/plik.db:/home/plik/server/plik.db
- ./data/plikd.cfg:/home/plik/server/plikd.cfg:ro
- ./data/background1.jpg:/home/plik/webapp/dist/img/background.jpg:ro
- ./data/settings.json:/home/plik/webapp/dist/settings.json:ro
Zwei Stolpersteine dabei:
- Datei vorher anlegen. Existiert
./data/plik.db auf dem Host noch nicht, legt Docker beim Mounten stattdessen ein Verzeichnis gleichen Namens an — die SQLite-Datei kann dann nicht geöffnet werden. touch ./data/plik.db
- Berechtigungen prüfen. Der Plik-Prozess im Container läuft standardmäßig als UID/GID
1000. Gehört plik.db auf dem Host root, kann der Prozess unter Umständen nicht schreiben: chown 1000:1000 ./data/plik.db
Wer statt SQLite postgres oder mysql als Metadaten-Backend konfiguriert hat, braucht diese Lösung nicht — dort ist die Persistenz ohnehin über den separaten Datenbank-Container/dessen Volume geregelt.
Reverse Proxy: Große Uploads nicht am Nginx scheitern lassen
Läuft vor Plik ein Reverse Proxy (bei mir Nginx Proxy Manager), lohnt sich ein Blick auf die Proxy-Konfiguration — Standardwerte sind für einen Filesharing-Dienst meist zu knapp bemessen:
location / {
proxy_pass http://plik-app:3000;
proxy_http_version 1.1;
proxy_request_buffering off;
proxy_buffering off;
client_max_body_size 16G;
proxy_read_timeout 3600s;
send_timeout 3600s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /stream/ {
proxy_pass http://plik-app:3000;
proxy_http_version 1.1;
proxy_request_buffering off;
proxy_buffering off;
client_max_body_size 0;
proxy_read_timeout 3600s;
send_timeout 3600s;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Wichtige Punkte:
proxy_request_buffering off und proxy_buffering off: Ohne das puffert Nginx große Uploads/Downloads erst komplett auf der Platte, bevor sie weitergereicht werden — unnötig langsam und speicherintensiv.
- Container-Name statt fester IP im
proxy_pass: stabiler gegenüber Docker-Netzwerk-Änderungen als eine feste IP wie 172.16.0.28.
X-Forwarded-Proto $scheme: Ohne diesen Header generiert Plik unter Umständen http://– statt https://-Links für Downloads.
- Separater
/stream/-Block mit client_max_body_size 0: Plik’s Streaming-Modus (Datei wird direkt vom Uploader zum Downloader durchgereicht, ohne serverseitige Zwischenspeicherung) braucht ein eigenes, unlimitiertes Body-Size-Limit.
Problem 2: Fake-Uploads durch Bots
Nach einiger Zeit im produktiven Betrieb tauchten wiederholt leere Uploads mit sinnlosem Text-Inhalt auf — ein klares Zeichen dafür, dass automatisierte Scanner die öffentlich erreichbare Instanz gefunden und den /upload-Endpoint blind befüllt haben.
Ursachenanalyse: Meine ursprüngliche Absicherung über die Environment-Variable PLIK_DISABLE_AUTHENTICATION=false griff vermutlich gar nicht:
- Plik-Env-Variablen nutzen den Präfix
PLIKD_, nicht PLIK_.
- Beim aktuellen
rootgg/plik:latest-Image gibt es das Feld DisableAuthentication ohnehin nicht mehr — Zugriffssteuerung läuft inzwischen über Feature-Flags.
Anonyme Uploads waren also faktisch weiterhin uneingeschränkt möglich.
Lösung — direkt in plikd.cfg:
FeatureAuthentication = "forced" # kein anonymer Upload mehr möglich
FeatureLocalLogin = "enabled"
Damit verlangt Plik für jeden Upload einen Login oder ein API-Token; reines Herunterladen über einen bestehenden Link bleibt für alle weiterhin möglich. Wer stattdessen (oder zusätzlich) nur bestimmten IP-Bereichen das Hochladen erlauben will:
UploadWhitelist = ["deine.ip.adresse/32"]
Zusätzliche Bremse auf Nginx-Ebene:
limit_req_zone $binary_remote_addr zone=plik_upload:10m rate=5r/m;
location / {
limit_req zone=plik_upload burst=3 nodelay;
# ... restliche Konfiguration wie oben
}
Das begrenzt automatisierte Massen-Requests pro IP spürbar, ohne normale Nutzung einzuschränken.
Fazit
Zwei Lektionen aus diesem Umbau:
- Bei jedem Self-Hosted-Dienst mit Datenbank prüfen, ob wirklich alle persistenten Pfade gemountet sind — nicht nur die offensichtlichen Datei-Verzeichnisse, sondern auch Metadaten-Datenbanken, Caches etc. Ein Blick in die Standard-Konfigurationsdatei des Projekts hilft, versteckte Pfade zu finden.
- Environment-Variablen-Namen und Config-Felder bei jedem Major-Update erneut prüfen. Was in einer älteren Version funktioniert hat, kann nach einem
:latest-Pull durch ein Refactoring des Projekts stillschweigend wirkungslos werden — im Zweifel lieber direkt in der gemounteten Config-Datei arbeiten statt über Env-Var-Mapping.
Wer Plik selbst betreibt, sollte in jedem Fall nach jedem Image-Update kurz die aktuelle plikd.cfg im Projekt-Repo mit der eigenen abgleichen — die Konfigurationsoptionen haben sich zwischen älteren und aktuellen Versionen spürbar verändert.