← Back to Wiki
Self-Hosting / File Sync

Self-Host a OneDrive Alternative with Seafile

Seafile is a lightweight, fast self-hosted file sync tool — a real alternative to OneDrive/ Dropbox for your own files. Here's the deployment, a genuine security incident worth learning from, a Cloudflare gotcha that only broke one specific client app, and the honest truth about what "automatic sync" can and can't mean on an iPhone.

Why Seafile over Nextcloud or Syncthing

Nextcloud can do file sync too, but it's a much heavier all-in-one suite if all you actually want is sync + share — same reasoning that applies any time you're picking a focused tool over a do-everything platform. Syncthing is lighter and fully peer-to-peer, but its story on iOS is weaker: it fights the same background execution restrictions covered below, without even the benefit of a proper client-server model. Seafile sits in the middle — a real server, a real desktop sync client on macOS/Linux/Windows, and a proper (if limited) iOS app.

Deployment

Standard Docker Compose: the official seafileltd/seafile-mc image, plus MariaDB and memcached. Point it at a reverse proxy (Caddy, nginx, whatever you're already running) with a real TLS cert if you want it reachable outside your LAN.

services:
  db:
    image: mariadb:10.11
    environment:
      - MYSQL_ROOT_PASSWORD=<generated>
    volumes:
      - /data/mysql:/var/lib/mysql

  memcached:
    image: memcached:1.6.29
    entrypoint: memcached -m 256

  seafile:
    image: seafileltd/seafile-mc:12.0-latest
    ports:
      - "80:80"
    volumes:
      - /data/seafile:/shared
    environment:
      - DB_HOST=db
      - DB_ROOT_PASSWD=<generated>
      - [email protected]
      - INIT_SEAFILE_ADMIN_PASSWORD=<generated>
      - SEAFILE_SERVER_HOSTNAME=files.example.com
      - SEAFILE_SERVER_PROTOCOL=https
      - JWT_PRIVATE_KEY=<generated>

A real security incident: one wrong env var name, and a public instance sat on default credentials

The env var names matter more than they look like they should. A first attempt used SEAFILE_ADMIN_EMAIL/SEAFILE_ADMIN_PASSWORD — a completely reasonable guess, and the convention plenty of other self-hosted apps actually use. This image doesn't. It wants INIT_SEAFILE_ADMIN_EMAIL/INIT_SEAFILE_ADMIN_PASSWORD (only found by reading /scripts/start.py inside the container directly — not obvious from the image's own docs).

Because the wrong env vars matched nothing the entrypoint script checked for, Docker did exactly what Docker always does with an env var nothing reads: silently ignored it. The container came up completely healthy, logged a normal-looking "Successfully created seafile admin" line, and fell back to its own hardcoded default account instead — [email protected] / asecret, a well-known, publicly documented default. Since this instance was reachable from the open internet (not LAN-gated), that meant a real admin panel sat on a public default credential from the moment it deployed, until a login attempt with the intended password failed and that's what actually surfaced the problem.

Lesson: verify the real login on any newly-public service, immediately, with an actual API call or login attempt — before handing off credentials to anyone. A clean container status and a "successfully created admin" log line are not proof your intended credentials took effect. This is a two-minute check that catches an entire class of silent, high-consequence bug:
curl -X POST https://files.example.com/api2/auth-token/ \
  -d "[email protected]&password=yourpassword"
# should return a real token — if it doesn't, don't assume "close enough"

Fixing the credentials without fighting Seafile's "Virtual ID" system

Seafile 12 introduced a privacy feature where any account created through the normal admin API gets an opaque, randomly-generated login (<random>@auth.local), with your real email stored only as a separate "contact email" — not the actual login. Creating a fresh account expecting a normal login produces an unusable random ID instead. The account created by the container's own first-boot init script doesn't go through this path, though — its literal email is the real login. If you hit this, don't fight the Virtual ID system; instead, link your real email as the contact email of the account that already works, and Seafile's own login flow will resolve it automatically.

A Cloudflare gotcha that broke exactly one client app

If you're behind Cloudflare and your iOS app reports something like "no available cert" or "no available certificates" when logging in — even with fully valid credentials and a properly-chained public TLS certificate — check whether your Cloudflare zone has a client certificate (mTLS) configured, even one you don't remember setting up on purpose.

Seafile's iOS TLS stack specifically mishandles an optional client-certificate request during the TLS handshake, even when nothing is actually enforcing it. If Cloudflare has a client cert available for your hostname, it sends an optional CertificateRequest during the handshake — most clients (browsers, curl, other iOS apps like Bitwarden or Immich) handle an optional request fine, connecting without ever presenting one. Seafile's iOS app apparently doesn't, and fails outright instead.

Fix: in Cloudflare, SSL/TLS → Client Certificates → find the certificate covering your domain → Edit its Hosts → remove the hostname (or the whole wildcard, if nothing else actually needs it). Verify the fix with a raw TLS handshake check rather than just retrying the app:

echo | openssl s_client -connect files.example.com:443 -servername files.example.com 2>&1 \
  | grep -i "client certificate"
# should show: No client certificate CA names sent

The honest truth about "automatic" sync on iPhone

If what you actually want is OneDrive-style invisible, continuous, background sync on an iPhone — the straightforward answer is: no third-party app achieves that, and this isn't a Seafile limitation specifically. Apple doesn't grant third-party apps the background execution privileges needed for continuous bidirectional file sync the way a desktop OS allows. This applies equally to OneDrive, Dropbox, and Google Drive's own iOS apps — what makes those feel automatic is Apple's own File Provider framework, a first-party integration point with elevated, system-coordinated background privileges a regular background task simply doesn't get.

Seafile's iOS app does implement a File Provider extension (so the iOS Files app can browse your Seafile libraries directly), but with a real limitation: non-encrypted libraries show up read-only in the Files app — fine for opening existing files from other apps, not a substitute for drag-and-drop uploads.

What actually works well automatically on iPhone: camera/photo backup specifically — Apple grants a distinct, more permissive background category for photo library backup that general file sync doesn't get. If what you're really after is "my photos back themselves up," a photo-focused tool (Immich, in this setup) will get you genuinely automatic background upload — general-purpose file sync tools won't, on any platform, due to the underlying iOS constraint.

Practical takeaway: use the desktop client on Mac/Linux/Windows for real always-on sync (no iOS restrictions apply there), and treat the iPhone app as something you open when you need it rather than something running invisibly in the background — that's the actual ceiling, not a configuration you're missing.