Same-day-as-deploy upgrade: image swapped to seafileltd/seafile-pro-mc:13.0-latest, elasticsearch:8.15.0 added as new compose service for Pro's full-text search. Free Pro tier (<=3 users, no license). Existing data + users survived. Also documented the Caddy patch stripping IETF resumable-upload headers (Upload-Draft-Interop-Version etc.) so the iOS Seafile Pro 4.0.2 app falls back to plain multipart upload; without it large uploads stalled and cancelled after ~60s.
9.4 KiB
125 — seafile
Seafile Pro Edition 13.0 — exploratory deployment to evaluate as a possible nextcloud (114) replacement. Stood up on 2026-05-12, upgraded from CE → Pro the same day (free tier, ≤3 users). No data migrated from NC; NC stays untouched.
At a glance
- Hostname:
seafile - IP:
192.168.8.185 - Privilege: privileged (Debian 12, nesting + keyctl)
- Resources: 4 cores / 8 GiB RAM / 32 GiB rootfs / 1 GiB swap
- Mounts:
/mnt/library↔/mnt/library(Seafile storage under/mnt/library/seafile/data, ES indices under/mnt/library/seafile/elasticsearch) - Public hostname:
files.hubris.network→ caddy (121) →192.168.8.185:80 - Reachable from: LAN + Netbird mesh (no public ingress)
- Edition: Pro free tier (no
seafile-license.txt, ≤3 users limit) —api2/server-info/returnsfeatures: [seafile-basic, seafile-pro, client-sso-via-local-browser]
Stack
Docker-compose at /opt/seafile/, four services on a private seafile-net bridge:
| Service | Image | Role |
|---|---|---|
seafile |
seafileltd/seafile-pro-mc:13.0-latest |
Seahub + seaf-server + Apache (Pro flavour; uses Redis for cache) |
db |
mariadb:10.11 |
ccnet / seafile / seahub databases |
redis |
redis:7-alpine |
session + cache backend |
elasticsearch |
elasticsearch:8.15.0 |
full-text search (Pro-only feature) |
Compose layout:
| File | Purpose |
|---|---|
/opt/seafile/seafile-server.yml |
upstream-verbatim from manual.seafile.com/13.0/repo/docker/ce/ (image swapped to pro-mc via .env) |
/opt/seafile/elasticsearch.yml |
upstream-derived; local tweaks for LXC — bootstrap.memory_lock=false, mem_limit: 2g, ES_JAVA_OPTS=-Xms1g -Xmx1g, no ulimits.memlock (privileged LXC can't reliably hold memlock=unlimited) |
/opt/seafile/docker-compose.override.yml |
local: publishes 192.168.8.185:80, extra_hosts: auth.hubris.network:192.168.8.175, drops bundled-caddy labels (labels: !reset []) |
/opt/seafile/.env |
mode 600, not git-tracked — SEAFILE_IMAGE=seafileltd/seafile-pro-mc:13.0-latest, SEAFILE_ELASTICSEARCH_IMAGE=elasticsearch:8.15.0, SEAFILE_ELASTICSEARCH_VOLUME=/mnt/library/seafile/elasticsearch, COMPOSE_FILE='seafile-server.yml,elasticsearch.yml,docker-compose.override.yml', DB passwords, JWT_PRIVATE_KEY, INIT_SEAFILE_ADMIN_*, SEAFILE_SERVER_HOSTNAME=files.hubris.network, SEAFILE_SERVER_PROTOCOL=https, ENABLE_SEADOC=false |
Do not rotate JWT_PRIVATE_KEY — it invalidates every active session and library access token.
Storage layout
/mnt/library/seafile/data/— bind-mounted to/sharedin the seafile container. Holds config (seafile/conf/), logs (seafile/logs/), and the heavy block/fs/commit trees (seafile/seafile-data/storage/). Group-owned:media2775./opt/seafile/mysql/— mariadb data (small, stays on rootfs).
Auth
Native OAuth2/OIDC against authentik (124). Configuration in /mnt/library/seafile/data/seafile/conf/seahub_settings.py between the # BEGIN-HUBRIS-OAUTH / # END-HUBRIS-OAUTH markers.
Authentik side:
- Provider:
Provider for Seafile(OAuth2/OIDC, confidential, signing key = self-signed cert) - Application slug:
seafile - Redirect URI:
https://files.hubris.network/oauth/callback/(trailing slash mandatory) - Launch URL:
https://files.hubris.network/ - Policy binding:
authentik Adminsgroup - Provisioned via
ak shellscript (idempotent —OAuth2Provider.objects.get_or_create(name="Provider for Seafile"))
Seahub side:
ENABLE_OAUTH = TrueOAUTH_AUTHORIZATION_URL/OAUTH_TOKEN_URL/OAUTH_USER_INFO_URL→https://auth.hubris.network/application/o/{authorize,token,userinfo}/OAUTH_SCOPE = ['openid', 'profile', 'email']OAUTH_ATTRIBUTE_MAP = {'sub': (True, 'uid'), 'email': (True, 'email'), 'name': (False, 'name')}—subis the stable identifier; without it the earlyif not uid:check renders the generic "Error, please contact administrator" page.OAUTH_CREATE_UNKNOWN_USER = True,OAUTH_ACTIVATE_USER_AFTER_CREATION = True— JIT provisioning enabled.CLIENT_SSO_VIA_LOCAL_BROWSER = True— required so the iOS Seafile Pro app bounces OAuth through Safari (system browser) instead of the in-app webview; the webview path cannot complete the round-trip back to the app.- Authentik provider scope mappings — the OAuth2 provider MUST have
openid,profile,emailscope mappings attached (property_mappings), otherwise userinfo returns onlysuband the OAUTH_ATTRIBUTE_MAP required-claim check fails.
The Seahub login page renders a "Single Sign-On" button that 302s through /sso/ → /oauth/login/ → Authentik flow.
Reverse-proxy settings (also in seahub_settings.py)
SERVICE_URL = "https://files.hubris.network"
FILE_SERVER_ROOT = "https://files.hubris.network/seafhttp"
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
CSRF_TRUSTED_ORIGINS = ['https://files.hubris.network']
DNS / mesh
- LAN clients resolve
files.hubris.network→192.168.8.175via dnsmasq on LXC 124 (entry:address=/files.hubris.network/192.168.8.175). - Netbird mesh peers reach
192.168.8.185through the existing192.168.8.0/24route advertised by the hubris peer. - The Seafile container itself needs
auth.hubris.network → 192.168.8.175mapped at the Docker level (Docker's resolver doesn't follow LXC/etc/hostsor the dnsmasq split-horizon), sodocker-compose.override.ymlcarriesextra_hosts: auth.hubris.network:192.168.8.175. See mesh migration for the pattern.
Caddy block (LXC 121)
files.hubris.network {
tls {
dns ionos {env.IONOS_AUTH_API_TOKEN}
}
encode zstd gzip
request_body {
max_size 10GB
}
reverse_proxy 192.168.8.185:80 {
header_up X-Forwarded-Proto https
header_up X-Forwarded-Host {host}
header_up Host {host}
# Strip IETF resumable-upload negotiation headers. The iOS Seafile Pro app
# (>=4.0) sends Upload-Draft-Interop-Version: 6 + Upload-Complete, but the
# bundled seafile-fileserver doesn't speak that draft. Without stripping,
# uploads hang ~60s waiting for an interim response and then cancel.
header_up -Upload-Draft-Interop-Version
header_up -Upload-Complete
header_up -Upload-Offset
header_up -Upload-Length
transport http {
dial_timeout 30s
read_timeout 1h
write_timeout 1h
}
flush_interval -1
}
}
No import authentik — Seafile handles OAuth natively, forward-auth would block the /oauth/callback/ round-trip.
Why exploration-only (and not a Nextcloud cutover)
The mulita (120) photo stack is tightly coupled to Nextcloud — it reads storage roots from /mnt/library/homecloud/{user}/files/, proxies thumbnails through NC's /core/preview, and consumes NC webhook_listeners for file events. Migrating off Nextcloud would require re-architecting all three integration points first.
For now: stand up Seafile, kick the tires, decide later whether to:
- Keep NC for photos and move docs/files to Seafile.
- Refactor mulita off NC, then full cutover.
- Roll back (Seafile is rollback-safe — NC is untouched throughout).
Related
Changelog
2026-05-12 — CE → Pro upgrade
Image swapped to seafileltd/seafile-pro-mc:13.0-latest; added elasticsearch:8.15.0 as a new service for Pro's full-text search. Free Pro tier (≤3 users, no license). MariaDB dump saved at /mnt/library/seafile/backups/seafile-all-20260512-183001.sql.gz (1.1 MB compressed) before the swap; recovery path is gunzip … | mariadb -uroot against a fresh CE container if needed. ES tuned for the LXC: bootstrap.memory_lock=false, no ulimits.memlock, 1 GiB JVM heap, 2 GiB container mem_limit. Host vm.max_map_count already at 1,048,576 (well above the 262,144 ES minimum). Existing data + JIT users (admin, dtoro) survived the migration; libraries intact (Iphone with 20 files / 53.1 MB carried through).
2026-05-12 — Caddy: strip IETF resumable-upload headers
Patched files.hubris.network block to drop Upload-Draft-Interop-Version, Upload-Complete, Upload-Offset, Upload-Length from the upstream-bound request, and bumped reverse_proxy timeouts (read_timeout 1h, write_timeout 1h). Reason: iOS Seafile Pro 4.0.2 negotiates resumable uploads via these headers; bundled fileserver doesn't speak the draft → uploads stalled ~60s and got cancelled. Stripping forces the client to fall back to plain multipart, which works.
2026-05-12 — initial deployment
LXC 125 created (privileged Debian 12, 4 c / 8 GiB / 32 GiB, IP 192.168.8.185). Seafile CE 13.0 docker-compose stack at /opt/seafile/, storage on /mnt/library/seafile/data. Authentik OAuth provider + Application provisioned via ak shell (slug seafile, redirect URI https://files.hubris.network/oauth/callback/). Caddy site block added on LXC 121, dnsmasq entry on LXC 124. End-to-end OAuth handshake verified to the Authentik flow page. No data migration — exploratory deployment alongside Nextcloud.