Zipline mit Docker Compose: Datei-Uploads und Kurzlinks selbst hosten
Zipline ist ein selbst gehosteter Upload-Dienst für Dateien und Screenshots mit eingebautem URL-Kürzer, kompatibel zu ShareX und Flameshot. Die Anleitung zeigt die Installation von Version 4.8.0 mit PostgreSQL per Docker Compose, den Upload per curl, getestete Berechtigungen, Reverse Proxy mit HTTPS-Links sowie ein Backup, das sich nachweislich zurückspielen lässt.
Geprüft am 10.10.2026 · für Zipline 4.8.0, PostgreSQL 16
Mit KI erstellt – redaktionelle Prüfung ausstehend
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Zipline ist ein selbst gehosteter Dienst für Datei- und Screenshot-Uploads mit eingebautem URL-Kürzer. Screenshot-Werkzeuge wie ShareX, Flameshot oder ein simples curl-Skript laden Dateien per API-Token hoch und bekommen sofort einen teilbaren Link zurück. Für KMU ist das eine Alternative zu öffentlichen Bildhostern: Supportfotos und Logauszüge bleiben auf dem eigenen Server. Diese Anleitung installiert Zipline 4.8.0 mit PostgreSQL per Docker Compose, prüft Upload, Kurzlinks und Berechtigungen und zeigt ein Backup, das sich nachweislich zurückspielen lässt.
Voraussetzungen
Im Leerlauf belegte Zipline im Test rund 180 MiB RAM, PostgreSQL rund 23 MiB.
- Linux-Server oder VM mit Docker Engine und Docker Compose v2, 1 bis 2 CPU-Kerne, 2 GB RAM genügen für ein kleines Team.
- Architektur x86_64 (amd64) oder ARM64: Das Image
ghcr.io/diced/zipline:4.8.0wird laut Registry-Manifest für beide Plattformen gebaut. - Rund 2 GB freier Platz für die Images (Zipline 1,05 GB,
postgres:16639 MB) plus Speicher für Ihre Dateien; Bildschirmvideos wachsen schnell. - Für den Betrieb außerhalb des LANs eine Domain mit TLS-Zertifikat und ein Reverse Proxy (Nginx, Caddy oder Nginx Proxy Manager).
curl,jqundopensslauf dem Server.
| Eckdaten | Wert |
|---|---|
| Projekt | diced/zipline, MIT-Lizenz, rund 3.400 GitHub-Sterne |
| Version | 4.8.0 (Release vom 27.09.2026), Image-Tag 4.8.0 ohne „v“ |
| Images | ghcr.io/diced/zipline:4.8.0, postgres:16 |
| Port | 3000/TCP (Weboberfläche und API) |
| Persistenz | Volume pgdata, Bind-Mounts ./uploads, ./public, ./themes |
| Pflicht-Variablen | POSTGRESQL_PASSWORD, CORE_SECRET (mindestens 32 Zeichen) |
| Healthcheck | GET /api/healthcheck liefert {"pass":true} |
Wer nur einen Dateimanager mit Freigaben sucht, ist mit FileBrowser Quantum oder copyparty besser bedient. Grenzen von Zipline: ein einzelner Maintainer, keine Ende-zu-Ende-Verschlüsselung, und der Container läuft als root, die Upload-Dateien auf dem Host gehören deshalb root.
Schritt 1: Projektordner, compose.yaml und .env anlegen
Die folgende compose.yaml entspricht der offiziellen docker-compose.yml aus dem Repository, mit einer Änderung: Statt latest ist die Version 4.8.0 festgeschrieben, damit ein docker compose pull nicht unbemerkt ein Major-Update einspielt. Achtung beim Tag: ghcr.io/diced/zipline:v4.8.0 existiert nicht, die Registry führt die Version ohne „v“.
sudo mkdir -p /opt/zipline && cd /opt/zipline
sudo nano compose.yaml
services:
postgresql:
image: postgres:16
restart: unless-stopped
env_file:
- .env
environment:
POSTGRES_USER: ${POSTGRESQL_USER:-zipline}
POSTGRES_PASSWORD: ${POSTGRESQL_PASSWORD:?POSTGRESQL_PASSWORD is required}
POSTGRES_DB: ${POSTGRESQL_DB:-zipline}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD', 'pg_isready', '-U', 'zipline']
interval: 10s
timeout: 5s
retries: 5
zipline:
image: ghcr.io/diced/zipline:4.8.0
restart: unless-stopped
ports:
- '3000:3000'
env_file:
- .env
environment:
- DATABASE_URL=postgres://${POSTGRESQL_USER:-zipline}:${POSTGRESQL_PASSWORD}@postgresql:5432/${POSTGRESQL_DB:-zipline}
depends_on:
postgresql:
condition: service_healthy
volumes:
- './uploads:/zipline/uploads'
- './public:/zipline/public'
- './themes:/zipline/themes'
healthcheck:
test: ['CMD', 'wget', '-q', '--spider', 'http://0.0.0.0:3000/api/healthcheck']
interval: 15s
timeout: 2s
retries: 2
volumes:
pgdata:
Heben Sie postgres:16 nicht eigenmächtig auf 18 an: Dort liegt das Volume unter /var/lib/postgresql, und ein Major-Wechsel braucht Dump und Restore.
Zwei Zufallswerte mit je 32 Zeichen erzeugt der Befehl aus der offiziellen Doku:
echo "POSTGRESQL_PASSWORD=$(openssl rand -base64 42 | tr -dc A-Za-z0-9 | cut -c -32 | tr -d '\n')" > .env
echo "CORE_SECRET=$(openssl rand -base64 42 | tr -dc A-Za-z0-9 | cut -c -32 | tr -d '\n')" >> .env
chmod 600 .env
Struktur der Datei (Platzhalterwerte):
# .env (Beispielwerte, nicht übernehmen)
POSTGRESQL_PASSWORD=ErsetzenDurch32ZufaelligeZeichen1
CORE_SECRET=ErsetzenDurchMindestens32Zeichen99
# erst nach Einrichtung des Reverse Proxy aktivieren:
# CORE_TRUST_PROXY=true
# CORE_TRUSTED_PROXIES=172.18.0.1
# CORE_RETURN_HTTPS_URLS=true
CORE_SECRET signiert Sitzungscookies und verschlüsselt API-Tokens. Bewahren Sie es zusätzlich im Passwortmanager auf.
Verifizieren: docker compose config --quiet endet ohne Ausgabe. Fehlt das Datenbankpasswort, bricht Compose bereits hier ab: required variable POSTGRESQL_PASSWORD is missing a value: POSTGRESQL_PASSWORD is required.
Schritt 2: Container starten und Healthchecks prüfen
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --no-log-prefix zipline | head
Im Test war Zipline nach gut 20 Sekunden healthy. Das Log nennt die Version:
[INFO server] starting zipline mode="production" version="4.8.0" argv=[]
[INFO db] connecting to database
[INFO server] server started hostname="0.0.0.0" port=3000
Verifizieren: Beide Dienste zeigen in docker compose ps den Status (healthy), und curl -s http://localhost:3000/api/healthcheck liefert {"pass":true}.
Schritt 3: Ersten Administrator anlegen
Beim ersten Aufruf von http://SERVER:3000 leitet Zipline auf eine Einrichtungsseite um, auf der Sie Benutzername und Passwort des Super-Administrators festlegen. Ohne Browser geht es per API; GET /api/setup verrät, ob die Instanz noch unkonfiguriert ist:
curl -s http://localhost:3000/api/setup
# {"firstSetup":true}
curl -s -H 'content-type: application/json' \
-d '{"username":"admin","password":"LANGES-PASSWORT"}' \
http://localhost:3000/api/setup | jq '.user.role'
# "SUPERADMIN"
Richten Sie die Instanz direkt nach dem Start ein: Solange firstSetup wahr ist, kann jeder, der Port 3000 erreicht, den Administrator anlegen. Ein zweiter Versuch nach der Einrichtung scheitert mit {"error":"E9001: Forbidden","code":9001,"statusCode":403}. Auch die Selbstregistrierung ist in 4.8.0 ab Werk aus, POST /api/auth/register antwortet mit E1037: User registration is disabled. Weitere Konten legen Administratoren unter Administrator, Users an.

Verifizieren: GET /api/setup liefert jetzt {"firstSetup":false}, und die Anmeldung im Browser führt zum Dashboard.
Schritt 4: Datei per API-Token hochladen und abrufen
Den Token finden Sie über den Benutzernamen oben rechts, Settings, Feld Token. Er hat vollen Kontozugriff; speichern Sie ihn in einer Datei mit Rechten 600.
TOKEN=$(cat ~/.zipline-token)
curl -s -H "authorization: $TOKEN" -F file=@bericht.png \
http://localhost:3000/api/upload | jq -r '.files[0].url'
# http://localhost:3000/u/2eOD40.png
/u/NAME zeigt die Datei an, /raw/NAME liefert Rohdaten; die SHA-256-Prüfsumme der Testdatei war identisch. HTTP-Header steuern Optionen pro Upload, etwa x-zipline-max-views: 1 für Einmal-Links: Der erste Abruf lieferte 200, jeder weitere 404. Mit x-zipline-password wird die Datei passwortgeschützt, /raw/NAME antwortet dann ohne Freigabe mit E3018: Invalid access token provided. und ein falsches Passwort mit E3005: Incorrect password.

Den Kurzlink-Dienst erreichen Sie über denselben Token:
curl -s -H "authorization: $TOKEN" -H 'content-type: application/json' \
-d '{"destination":"https://s-edv.com/"}' \
http://localhost:3000/api/user/urls | jq -r .url
# http://localhost:3000/go/o97dvD
/go/o97dvD antwortete mit HTTP 302 auf das Ziel. Mehr Funktionen bietet Shlink.
Verifizieren: Der Upload liefert JSON mit files[0].url, die Datei erscheint im Ordner uploads und im Dashboard unter Files.
Schritt 5: Berechtigungen gegenprüfen
Ein erfolgreicher Upload beweist noch nichts über die Zugriffskontrolle. Prüfen Sie deshalb auch die Fälle, die scheitern müssen:
- Upload ohne Token: HTTP 401,
E2000: Invalid login session. - Upload mit verändertem Token: HTTP 401,
E2001: Invalid token. - Normaler Benutzer liest oder löscht die Datei eines anderen über
/api/user/files/ID: HTTP 404,E4000: File not found. Zipline verrät damit nicht einmal, dass die Datei existiert. - Normaler Benutzer ruft
/api/usersoder/api/server/settingsauf: HTTP 403,E3000: Admin only. - Anmeldung mit falschem Passwort: HTTP 400,
E1044: Invalid username or password.
Öffentliche Links sind ohne Anmeldung abrufbar, das ist der Zweck des Dienstes. Schnelle Wiederholungen beantwortet der Rate-Limiter mit HTTP 429 (Rate limit exceeded, retry in 5 seconds).

Verifizieren: Alle fünf Aufrufe liefern den genannten Fehlercode, die Datei des Administrators bleibt unter /raw/NAME mit 200 abrufbar.
Schritt 6: Reverse Proxy, TLS und ShareX
Hinter einem Proxy gibt Zipline zunächst Links mit http:// zurück. Das ist im Test genau so aufgetreten. Abhilfe schaffen drei Variablen in der .env: CORE_TRUST_PROXY=true, CORE_TRUSTED_PROXIES mit der Adresse des Proxys, wie sie der Container sieht, und CORE_RETURN_HTTPS_URLS=true. Bei einem Proxy auf dem Host kommen Anfragen vom Gateway des Compose-Netzes (im Test 172.18.0.1, eingetragen war das Docker-Netz 172.16.0.0/12; enger ist besser). Niemals 0.0.0.0/0 eintragen, sonst fälschen Clients ihre IP. Danach lieferte der Upload https://-Links. Nginx-Beispiel:
server {
listen 443 ssl;
server_name files.example.de;
ssl_certificate /etc/ssl/files.example.de.pem;
ssl_certificate_key /etc/ssl/files.example.de.key;
client_max_body_size 100M;
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
}
client_max_body_size sollte zur Upload-Grenze passen; die Oberfläche zeigt ab Werk „100 MB limit per file“. Mit Caddy reicht reverse_proxy localhost:3000, Details in unserer Caddy-Anleitung. Binden Sie Port 3000 danach nur noch an 127.0.0.1:3000:3000, damit niemand den Proxy umgeht.
Für ShareX erzeugt Zipline die Konfiguration selbst: Settings, Abschnitt Generate Uploaders, ShareX. Im Dialog wählen Sie Upload File oder Shorten URL, Namensformat, Kompression und Abrufgrenze, dann Download. Die .sxcu-Datei importieren Sie per Doppelklick in ShareX. Auch Flameshot und ein Shell-Skript stehen bereit.

Verifizieren: Ein Upload über die Domain liefert eine URL mit https://, und curl -I https://files.example.de/api/healthcheck antwortet mit 200.
Schritt 7: Backup und Restore
Metadaten, Benutzer und Kurzlinks liegen in PostgreSQL, die Dateien in uploads. Sichern Sie beides bei gestoppter Anwendung:
cd /opt/zipline && mkdir -p backup
docker compose stop zipline
docker compose exec -T postgresql pg_dump -U zipline zipline > backup/zipline_db.sql
tar czf backup/zipline_files.tgz uploads public themes
docker compose start zipline
Kopieren Sie backup/, compose.yaml und .env auf ein anderes System. Restore nach gelöschter Datenbank und geleertem Upload-Ordner:
docker compose stop zipline
docker compose exec -T postgresql psql -U zipline -d postgres -c 'DROP DATABASE zipline;'
docker compose exec -T postgresql psql -U zipline -d postgres -c 'CREATE DATABASE zipline OWNER zipline;'
docker compose exec -T postgresql psql -v ON_ERROR_STOP=1 -q -U zipline zipline < backup/zipline_db.sql
sudo tar xzf backup/zipline_files.tgz
docker compose start zipline
Ergebnis: Zipline war nach dem Start wieder healthy, /api/user/files?page=1 listete alle drei Testdateien, die Prüfsumme der Binärdatei war identisch, und der alte API-Token funktionierte weiter (HTTP 200). Letzteres gilt nur, weil das CORE_SECRET gleich blieb. Mit einem neuen Secret antwortete derselbe Token mit E2001: Invalid token, alle Benutzer müssen sich neu anmelden und neue Tokens in ShareX hinterlegen. Weitere Strategien für Dumps beschreibt die Anleitung PostgreSQL mit pg_dump und pg_restore sichern.
Verifizieren: Der Restore endet mit Exit-Code 0, Dateien sind im Dashboard sichtbar und unter /raw/NAME abrufbar.
Schritt 8: Updates, Rollback und Entfernen
Für ein Update tragen Sie den neuen Tag in compose.yaml ein, sichern wie in Schritt 7 und führen docker compose pull und docker compose up -d aus. Lesen Sie vorher die Release-Hinweise: Zipline 4.8 hat Prisma durch Drizzle ersetzt und migriert die Datenbank beim ersten Start einmalig; ältere Instanzen als 4.7.0 müssen zuerst auf 4.7.0. Rollback heißt deshalb: alten Tag eintragen und Datenbank samt Uploads aus dem Backup vor dem Update zurückspielen. Nur den Tag zurückzudrehen reicht nicht.
Zum Entfernen genügt docker compose down. Warnung: docker compose down -v löscht das Volume pgdata mit allen Benutzern, Links und Metadaten endgültig, und rm -rf /opt/zipline entfernt zusätzlich alle hochgeladenen Dateien. Führen Sie beides nur mit geprüftem Backup aus.
Verifizieren: Nach einem Update zeigt das Startprotokoll die neue Versionsnummer, und die Testdatei ist weiterhin abrufbar.
Troubleshooting
- Container startet in Schleife, Log zeigt
CORE_SECRET: Invalid input: expected string, received undefined: Die Variable fehlt in der.env. Wert erzeugen unddocker compose up -d. CORE_SECRET: Secret must contain at least 32 characters: Der Wert ist zu kurz. Mindestens 32 Zeichen verwenden.- Alle Tokens melden
E2001: Invalid token: DasCORE_SECRETwurde geändert. Altes Secret zurückspielen oder neue Tokens verteilen. - Pull scheitert, Tag nicht gefunden: Image-Tag mit „v“ angegeben. Die Registry beantwortet
v4.8.0mit 404, richtig ist4.8.0. - Links beginnen mit
http://trotz HTTPS:CORE_TRUST_PROXY,CORE_TRUSTED_PROXIESundCORE_RETURN_HTTPS_URLSsetzen (Schritt 6). /api/user/filesliefert 400 mitexpected number, received NaN: Der Parameter?page=1fehlt.
Häufige Fragen
Brauche ich PostgreSQL zwingend?
Nein. Zipline unterstützt alternativ PGlite, eine eingebettete Datenbank in einem Verzeichnis (DATABASE_URL=pglite:///zipline/data/database). PostgreSQL ist für Teams die robustere Wahl.
Wie schütze ich das Admin-Konto zusätzlich?
Zipline bietet TOTP-Zweifaktor und Passkeys in den Benutzereinstellungen sowie OAuth und OIDC, etwa mit Authentik oder Keycloak. Für alle, die die Instanz mit anderen teilen, empfiehlt der Hardening-Leitfaden mindestens eine dieser Optionen.
Wer kann eine hochgeladene Datei sehen?
Jeder, der den Link kennt. Die Dateinamen sind zufällig, aber nicht geheim. Für sensible Inhalte nutzen Sie Passwortschutz, Ablaufzeit (x-zipline-deletes-at) oder eine Abrufgrenze.
Fazit
Zipline ist mit zwei Containern und zwei Pflichtvariablen schnell betriebsbereit und deckt Screenshot-Uploads, Einmal-Links und Kurzlinks in einer Oberfläche ab. Die Zugriffskontrolle hielt im Test allen Gegenversuchen stand. Kritisch sind das CORE_SECRET und die gemeinsame Sicherung von Datenbank und Upload-Ordner. Mit festem Tag und Backup vor jedem Update bleibt der Dienst pflegeleicht.
Weiterführende Anleitungen und Quellen
- FileBrowser Quantum mit Docker Compose
- copyparty als Dateiserver mit WebDAV
- Shlink als URL-Kürzer selbst hosten
- PostgreSQL mit pg_dump und pg_restore sichern
- Zipline auf GitHub
- Offizielle Docker-Installation
- Release-Hinweise und Upgrade auf 4.8
- Reverse-Proxy-Leitfaden
- Hardening-Leitfaden
- PostgreSQL-Backup laut Projektdoku


