SkySend selbst hosten: E2E-verschlüsseltes Teilen mit Docker Compose
Große Dateien oder geheime Notizen teilen, ohne dass der Server sie je zu Gesicht bekommt: SkySend verschlüsselt Uploads direkt im Browser (AES-256-GCM) – auf dem Server liegt nur Chiffrat, der Schlüssel verlässt dein Gerät nie. In diesem Tutorial richtest du dein eigenes Zero-Knowledge-Sharing mit Docker Compose ein – inklusive Limits, Ablaufzeiten, Quota und Reverse Proxy.
Warum SkySend?
E-Mail-Anhänge scheitern an Größenlimits, Messenger komprimieren die Qualität und Cloud-Dienste lesen mit – wer einmal eine 3 GB-Datei verschicken wollte, kennt das Problem. SkySend ist ein minimalistischer, selbst hostbarer Dienst zum Teilen von Dateien und Notizen mit Ende-zu-Ende-Verschlüsselung: keine Konten, kein Tracking, keine fremden Server.
Konzept: Zero Knowledge
SkySend funktioniert nach demselben Prinzip wie das Vorbild-Paar timvisee/send (Dateien) und PrivateBin (Notizen):
- Verschlüsselung im Browser: AES-256-GCM (streaming, 64-KB-Blöcke); Schlüsselableitung über HKDF-SHA256, bei Passwortschutz zusätzlich Argon2id.
- Schlüssel nur in der URL: Er steht ausschließlich im Fragment (
#…) und wird nie zum Server gesendet. - Server ohne Wissen: Gespeichert werden nur verschlüsselte Blobs und Metadaten – selbst ein Admin kann Inhalte nicht lesen.
- Kontrolle über Lebensdauer: Ablaufzeit, Download-Limit, Passwort und „Burn after reading“ – danach werden die Daten gelöscht.
Vorbereitung: Ordner & Volumes
SkySend braucht zwei Datenverzeichnisse, die beide gemountet werden müssen. Ohne Volumes gehen beim Container-Neubau alle Uploads und der Datenbankstand verloren:
mkdir -p ~/skysend/data ~/skysend/uploads
cd ~/skysend
printf 'PUID=1000\nPGID=1000\n' > .env
sudo chown -R 1000:1000 data uploads
./data→/data: SQLite-Datenbank (/data/db/skysend.db), Metadaten und Branding (/data/branding)../uploads→/uploads: die verschlüsselten Upload-Dateien.
${PUID:-1000}-Fallbacks in der Compose-Datei startet der Stack auch ohne .env – dann läuft der Prozess als UID 1000.
Docker Compose – die Datei
So sieht das fertige Setup aus (deine Vorlage, aufgeräumt: leerer networks: {}-Block entfernt, Ports in Anführungszeichen):
services:
skysend:
image: skyfay/skysend:latest
container_name: skysend
restart: unless-stopped
deploy:
resources:
limits:
cpus: "4.0"
memory: 4G
ports:
- "127.0.0.1:50548:3000" # Web-UI nur lokal
volumes:
- ./data:/data # DB, Metadaten & Branding
- ./uploads:/uploads # verschlüsselte Dateien
environment:
- BASE_URL=https://sky.huuu.biz:50548 # öffentliche URL für Teilen-Links
- FILE_MAX_SIZE=4GB
- FILE_MAX_FILES_PER_UPLOAD=250
- FILE_EXPIRE_OPTIONS_SEC=600,3600,86400,172800,604800,1209600
- FILE_DEFAULT_EXPIRE_SEC=600
- FILE_DOWNLOAD_OPTIONS=1,2,5,10,20,50,100
- FILE_DEFAULT_DOWNLOAD=1
# - FILE_UPLOAD_QUOTA_BYTES=110GB # Quota (deaktiviert)
# - FILE_UPLOAD_CONCURRENT_CHUNKS=10 # parallele Chunks
- CUSTOM_LINK_URL=https://huuu.biz/#contact # Footer-Link
- CUSTOM_LINK_NAME=DMCA
- CUSTOM_COLOR=474f63
- PUID=${PUID:-1000}
- PGID=${PGID:-1000}
# Alle Optionen: https://docs.skysend.app/user-guide/configuration/environment-variables
Warum die einzelnen Zeilen?
BASE_URL: Aus dieser Adresse baut SkySend alle Teilen-Links – sie muss exakt der öffentlichen URL entsprechen (Details im Proxy-Abschnitt).127.0.0.1:50548:3000: Die Web-UI lauscht intern auf Port 3000; gebunden wird nur an localhost, der Zugriff von außen läuft über den Reverse Proxy.FILE_*: Dateigröße (4 GB), Dateien je Upload (250) sowie wählbare Ablauf- und Download-Limits – Standard ist hier 10 Minuten bei 1 Download.CUSTOM_*: eigener Footer-Link (z. B. DMCA) samt Anzeigename und Primärfarbe der UI (474f63, das#-Präfix ist optional).
Konfiguration & Umgebungsvariablen
Die wichtigsten Werte aus deiner Konfiguration im Überblick:
| Variable | Wert | Bedeutung |
|---|---|---|
BASE_URL | erforderlich | Öffentliche Instanz-URL; dient CORS und allen generierten Teilen-Links. |
FILE_MAX_SIZE | 4GB | Maximale Dateigröße pro Upload (Default: 2 GB). |
FILE_MAX_FILES_PER_UPLOAD | 250 | Max. Dateien je Multi-Upload (Default: 32). |
FILE_EXPIRE_OPTIONS_SEC | 10 Min. … 14 Tage | Auswählbare Ablaufzeiten in Sekunden. |
FILE_DEFAULT_EXPIRE_SEC | 600 | Vorausgewählte Ablaufzeit – muss in den Optionen enthalten sein. |
FILE_DOWNLOAD_OPTIONS | 1–100 | Auswählbare Download-Limits; 1 = „nur einmal lesbar“. |
CUSTOM_LINK_URL/NAME | DMCA | Eigener Footer-Link, z. B. auf eine Kontakt-/DMCA-Seite. |
CUSTOM_COLOR | 474f63 | Primärfarbe der UI als 6-stelliger Hex-Code. |
PUID/PGID | 1000 | UID/GID, unter denen der Container-Prozess läuft. |
Weitere praktische Optionen aus der offiziellen Referenz:
TRUST_PROXY=true: hinter einem Reverse Proxy setzen, damit Rate-Limits und Quotas die echte Client-IP sehen.FILE_UPLOAD_WS(Defaulttrue): Uploads laufen über eine WebSocket-Verbindung und umgehen damit HTTP/2-Engpässe von Proxies; Clients fallen sonst automatisch auf HTTP-Chunked-Upload zurück.FILE_UPLOAD_QUOTA_BYTES+FILE_UPLOAD_CONCURRENT_CHUNKS: Upload-Quota (z. B.110GB) bzw. parallele Chunks für schnellere Uploads in Chromium-Browsern.CUSTOM_TITLE,CUSTOM_PRIVACY,CUSTOM_LEGAL: Titel bzw. Footer-Links auf Datenschutz/Impressum;CUSTOM_LOGOüber/data/branding.STORAGE_BACKEND=s3: S3-kompatibler Speicher statt Dateisystem (R2, MinIO, Hetzner …).
Quotas bleiben dabei privat: SkySend speichert nur HMAC-gehashte IPs mit täglich rotierendem Schlüssel – kein Klartext, keine Korrelation über Tage hinweg.
Reverse Proxy, BASE_URL & HTTPS
Der Container spricht nur HTTP – also gehört ein TLS-terminierender Proxy davor. Da der Port nur auf localhost lauscht, reicht ein Proxy auf demselben Host. Wichtig für SkySend: WebSocket-Upgrades (Upload-Transport) und kein Body-Limit (Uploads bis 4 GB regelt die App):
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name sky.meine.domain;
access_log off;
error_log /var/log/nginx/sky.meine.domain.error.log;
ssl_certificate /etc/ssl/private/sky.meine.domain_ecc/fullchain.cer;
ssl_certificate_key /etc/ssl/private/sky.meine.domain_ecc/sky.meine.domain.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_early_data on;
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
# Sicherheits-Header
add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "no-referrer" always; # schützt den Schlüssel im URL-Fragment
# Uploads bis FILE_MAX_SIZE (4GB) zulassen – Limit prüft SkySend selbst
client_max_body_size 0;
location / {
proxy_http_version 1.1;
# WebSocket-Transport für Uploads (FILE_UPLOAD_WS)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Scheme $scheme;
# lange Uploads nicht abbrechen
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_pass http://127.0.0.1:50548;
}
}
Lief der Dienst vorher direkt unter https://sky.huuu.biz:50548, genügt nach dem Proxy-Umzug
die Änderung von BASE_URL=https://sky.huuu.biz (ohne Port!) und TRUST_PROXY=true
in der Compose-Datei – danach Container neu erstellen: docker compose up -d --force-recreate.
#…).
Über unverschlüsseltes HTTP wäre er im Klartext unterwegs – und damit wäre die Browser-Verschlüsselung wertlos.
Erster Start & Nutzung
cd ~/skysend
docker compose up -d
docker compose logs -f skysend
curl -s http://127.0.0.1:50548/api/health # {"status":"ok", ...}
Danach die Instanz-URL öffnen (hier https://sky.huuu.biz:50548) – es gibt kein Admin-Konto, die Seite ist sofort nutzbar:
- Dateien: Drag & Drop, mehrere Dateien/Ordner, optionales Passwort, Ablaufzeit und Download-Limit wählen → Link teilen.
- Notizen: Markdown-Text, Passwörter, Code oder SSH-Keys (Ed25519/RSA) – wahlweise „Burn after reading“.
- Dashboard im Browser: hochgeladene Einträge mit Live-Status und Auto-Cleanup (IndexedDB, bleibt lokal).
- CLI: Das Kommandozeilen-Tool nutzt dieselbe Ende-zu-Ende-Verschlüsselung:
skysend upload ./datei.pdf.
Wer den Link hat, kann lesen – mehr nicht. Konten gibt es keine.
Backups, Quotas & Updates
Für Updates reicht docker compose pull && docker compose up -d –
Daten bleiben dank Volumes erhalten. :latest folgt dabei der neuesten Veröffentlichung; für
reproduzierbare Stände lässt sich das Image auf einen konkreten Versions-Tag pinnen.
./data (Datenbank!) und ./uploads
gemeinsam, damit Metadaten und Blobs zusammenpassen. Wer die auskommentierten Zeilen aktiviert
(FILE_UPLOAD_QUOTA_BYTES=110GB), begrenzt das Upload-Volumen pro Nutzer – ohne dass IPs gespeichert werden.
Häufige Probleme (FAQ)
| Problem | Lösung |
|---|---|
| Teilen-Link zeigt falsche Domain/Port | BASE_URL exakt auf die öffentliche URL setzen – mit Port nur, wenn der wirklich offen ist. |
| Upload langsam oder bricht ab | Chromium + HTTP/2-Proxy: FILE_UPLOAD_CONCURRENT_CHUNKS erhöhen oder WebSocket-Transport prüfen; Proxy-Timeouts erhöhen. |
| Rate-Limit trotz wenig Traffic | Hinter Proxy TRUST_PROXY=true setzen – sonst zählt der Proxy als ein einziger Client. |
| Daten nach Container-Neubau weg | Beide Volumes (/data und /uploads) müssen gemountet sein. |
| Ablaufzeit/Download-Limit nicht wählbar | Der Default-Wert muss in der jeweiligen Options-Liste enthalten sein (z. B. 600 in FILE_EXPIRE_OPTIONS_SEC). |
| Port-Konflikt beim Start | Externen Port prüfen (hier 50548) – nicht doppelt in anderen Stacks vergeben. |
Fazit
SkySend ist in zehn Minuten aufgesetzt und danach erstaunlich unauffällig im Betrieb: keine Konten, keine Telemetrie, zwei Datenordner. Der Clou bleibt das Konzept – selbst dein eigener Server kann die Inhalte nicht lesen, weil die Verschlüsselung im Browser passiert. Kombiniert mit HTTPS davor, einem sauberen Proxy (WebSockets!) und regelmäßigem Backup ist das Teilen so privat, wie Teilen nur sein kann.
/data + /uploads) mounten. ③ BASE_URL exakt setzen,
hinter Proxy TRUST_PROXY=true und WebSockets durchreichen. ④ Immer HTTPS – sonst ist der Schlüssel wertlos.