Skip to content

Seafile Pro

Seafile Pro is private cloud storage with browser-based Office editing (OnlyOffice), collaborative document editing (SeaDoc), full-text search (SeaSearch) and antivirus scanning (ClamAV). Its services are split over Compose files that COMPOSE_FILE combines — the example enables seven — and it needs a commercial license. OnlyOffice is a separate server and is enabled only when ONLYOFFICE_JWT_SECRET is set; the example ships it empty.

Last run on a host 8 Nov 2026 · data restored from a backup

Pinned at 13.0.29 (released 4 Nov 2026) · upstream has released 14.0.9 on 13 Oct 2026

In the upstream images: 23 CRITICAL · 783 HIGH with a published fix — per image

To try it: Docker, and a Seafile Pro commercial licence, which the image requires either way. See Try it locally.

To deploy it behind the proxy: Traefik running, a hostname, a Seafile Pro commercial license, and an external OnlyOffice instance for .docx/.xlsx/.pptx editing (optional — all other features work without it).

On your own machine, with no proxy, domain or certificate. From apps/seafile-pro/:

Terminal window
cp .env.local.example .env.local
# The secrets ship empty; generate the ones this stack needs
{
echo "SEAFILE_MYSQL_DB_PASSWORD=$(openssl rand -base64 32 | tr -d '\n')"
echo "INIT_SEAFILE_MYSQL_ROOT_PASSWORD=$(openssl rand -base64 32 | tr -d '\n')"
echo "REDIS_PASSWORD=$(openssl rand -hex 32)"
echo "INIT_SEAFILE_ADMIN_PASSWORD=$(openssl rand -base64 32 | tr -d '\n')"
echo "JWT_PRIVATE_KEY=$(openssl rand -base64 48 | tr -d '\n')"
} >> .env.local
mkdir -p volumes/local/mysql volumes/local/redis volumes/local/seafile-data
sudo chown 999:999 volumes/local/mysql
docker compose -f docker-compose.local.yml --env-file .env.local up -d

Then open http://127.0.0.1:8001. Stop it again with the same command and down.

From apps/seafile-pro/:

  1. Terminal window
    cp .env.example .env
  2. Set required values in .env:

    APP_TRAEFIK_HOST=files.example.com
    SEAFILE_ADMIN_EMAIL[email protected]
    TZ=Europe/Vienna
  3. Generate passwords and the JWT key:

    Terminal window
    sed -i "s|^INIT_SEAFILE_MYSQL_ROOT_PASSWORD=.*|INIT_SEAFILE_MYSQL_ROOT_PASSWORD=$(openssl rand -base64 32 | tr -d '\n')|" .env
    sed -i "s|^SEAFILE_MYSQL_DB_PASSWORD=.*|SEAFILE_MYSQL_DB_PASSWORD=$(openssl rand -base64 32 | tr -d '\n')|" .env
    sed -i "s|^INIT_SEAFILE_ADMIN_PASSWORD=.*|INIT_SEAFILE_ADMIN_PASSWORD=$(openssl rand -base64 32 | tr -d '\n')|" .env
    sed -i "s|^JWT_PRIVATE_KEY=.*|JWT_PRIVATE_KEY=$(openssl rand -base64 48 | tr -d '\n')|" .env
    sed -i "s|^REDIS_PASSWORD=.*|REDIS_PASSWORD=$(openssl rand -hex 32)|" .env
  4. If using OnlyOffice, set the hostname and copy the JWT secret — it must match the OnlyOffice server exactly:

    ONLYOFFICE_HOST=office.example.com
    Terminal window
    # sed -i "s|^ONLYOFFICE_JWT_SECRET=.*|ONLYOFFICE_JWT_SECRET=<value-from-apps/onlyoffice>|" .env
  5. Pull images:

    Terminal window
    docker compose pull
  6. Start the stack:

    Terminal window
    docker compose up -d
  7. Wait for all containers to be healthy — first boot initialises the databases and takes roughly three minutes:

    Terminal window
    docker compose ps
  8. Required: restart the app container once to inject Blueprint configs into Seafile’s settings files. This step only needs to run once — a marker prevents it from repeating on subsequent restarts:

    Terminal window
    docker compose restart seafile-pro-app
  9. Build the initial full-text search index:

    Terminal window
    docker exec seafile-pro-app /opt/seafile/seafile-server-latest/pro/pro.py search --update
Terminal window
docker compose ps # every service of the files in COMPOSE_FILE up; app, db, redis, clamav should show healthy

Confirm the config injection from step 8 ran:

Terminal window
docker exec seafile-pro-app grep "Blueprint" /shared/seafile/conf/seahub_settings.py
docker exec seafile-pro-app grep "SEASEARCH" /shared/seafile/conf/seafevents.conf
docker exec seafile-pro-app grep "virus_scan" /shared/seafile/conf/seafile.conf

Each command should return a matching line. No output means the injection step was skipped — run docker compose restart seafile-pro-app and recheck.

Confirm the notification server is reachable:

Terminal window
curl -s https://your-domain/notification/ping # {"ret": "pong"}

Test antivirus:

Terminal window
docker exec seafile-pro-app bash -c "curl -s https://secure.eicar.org/eicar.com.txt | clamdscan -"
# Expected: stream: Eicar-Test-Signature FOUND

Then in the browser, confirm each service works:

  • Log in with the admin account.
  • Upload a file and download it back.
  • Create and edit a Markdown (.md) file.
  • Open a .docx in the OnlyOffice editor (requires OnlyOffice configured).
  • Create a .sdoc file and open it in SeaDoc.
  • Search for a word from an uploaded file — SeaSearch should return it.

OnlyOffice embeds a browser-based Office editor into Seafile. It runs as a separate server — either another Blueprint stack or a different machine — and both sides share the same JWT secret.

Server-to-server path: OnlyOffice fetches the document from Seafile and saves it back through a callback. Seafile’s route ships closed, and a closed route refuses every container, so on the same host these two paths have their own opt-in routes in onlyoffice-callback.yml (the same mechanism as Seafile CE); an OnlyOffice server on another host reaches a closed Seafile only by its address in TRUSTED_RANGES. DOCX, XLSX and PPTX open, save and reopen through onlyoffice-callback.yml.

Allowed origins: On the OnlyOffice server, add the Seafile origin to ONLYOFFICE_ALLOWED_ORIGINS in its .env so browsers are permitted to embed the editor in an iframe. In the Blueprint’s apps/onlyoffice stack this value is embedded in a Traefik label — changing it takes effect only after recreating the container, not after a plain restart:

Terminal window
# On the OnlyOffice server:
docker compose up -d --force-recreate

SMTP is optional but required for password reset emails, share invitations, and file change notifications. Set the SMTP variables in .env:

SEAFILE_SMTP_HOST=smtp.example.com
SEAFILE_SMTP_PORT=587
SEAFILE_SMTP_USER[email protected]
SEAFILE_SMTP_FROM[email protected]
SEAFILE_SMTP_USE_TLS=true
SEAFILE_SMTP_PASSWORD=your-smtp-password

Then recreate the app so it picks up the new values — docker compose restart keeps the environment the container was created with, so a changed .env would have no effect:

Terminal window
docker compose up -d seafile-pro-app

Send a test email from the Seafile admin panel before relying on this for password resets.

Backup and restore covers the host side — Borgmatic, the repository, the schedule and the database hooks. What this stack contributes:

  • volumes/seafile-data/ — all user files and library data
  • volumes/mysql/ — all three databases (Seafile, Seahub, Ccnet)
  • volumes/seadoc-data/ — collaborative document state
  • The .env file and any secrets

The search index is not on that list. It is rebuilt rather than restored, with the command under Going further.

The databases and the file volumes have to come back from the same point in time, and all three databases together. A restore that recovers one side without the other leaves the installation inconsistent rather than merely incomplete.

The database password lives in .env, and borgmatic reads credentials from a file, so write one for it first:

Terminal window
mkdir -p .secrets
grep '^SEAFILE_MYSQL_DB_PASSWORD=' .env | cut -d= -f2- | tr -d '\n' > .secrets/seafile_db_pwd.txt
chmod 600 .secrets/seafile_db_pwd.txt

For borgmatic:

mariadb_databases:
- name: ccnet_db
container: seafile-pro-db
username: seafile
password: "{credential file /srv/docker/apps/seafile-pro/.secrets/seafile_db_pwd.txt}"
- name: seafile_db
container: seafile-pro-db
username: seafile
password: "{credential file /srv/docker/apps/seafile-pro/.secrets/seafile_db_pwd.txt}"
- name: seahub_db
container: seafile-pro-db
username: seafile
password: "{credential file /srv/docker/apps/seafile-pro/.secrets/seafile_db_pwd.txt}"

Restore order: databases, then the block store, then the application, then the search reindex. A dump holds the databases, not the account Seafile connects with — restored into a fresh db container, every request fails with Access denied for user 'seafile'. Create the account before starting the app:

Terminal window
docker compose up -d db
# restore the three dumps, then:
docker compose exec -T db sh -c 'mariadb -uroot -p"$MYSQL_ROOT_PASSWORD"' <<SQL
CREATE USER IF NOT EXISTS 'seafile'@'%' IDENTIFIED BY '$(grep '^SEAFILE_MYSQL_DB_PASSWORD=' .env | cut -d= -f2- | tr -d '\n')';
GRANT ALL PRIVILEGES ON ccnet_db.* TO 'seafile'@'%';
GRANT ALL PRIVILEGES ON seafile_db.* TO 'seafile'@'%';
GRANT ALL PRIVILEGES ON seahub_db.* TO 'seafile'@'%';
SQL
docker compose up -d

This sequence follows the upstream Docker upgrade guide.

  1. Read the Seafile Pro release notes and the Docker upgrade guide for breaking changes.

  2. Back up all volumes before touching the stack.

  3. Update image tags in .env — APP_TAG for the main server, and the tags for any component services that changed.

  4. Pull and recreate:

    Terminal window
    docker compose pull
    docker compose up -d
  5. Watch app logs for any database migration output:

    Terminal window
    docker compose logs -f seafile-pro-app
  6. Verify login, file access, editing, and search after the update.

OnlyOffice editor not loading: either a JWT mismatch — ONLYOFFICE_JWT_SECRET in apps/seafile-pro/.env has to match the secret on the OnlyOffice server exactly — or the Seafile origin is missing from ONLYOFFICE_ALLOWED_ORIGINS on the OnlyOffice side.

Allowed-origins change has no effect: ONLYOFFICE_ALLOWED_ORIGINS is embedded in a Traefik label at container creation time, so a plain docker compose restart does not pick up a new value. Run docker compose up -d --force-recreate on the OnlyOffice server.

ClamAV socket warning in startup logs: the line Clamd was NOT notified: Can't connect to clamd through /tmp/clamd.sock is a timing race during container initialisation and is non-fatal. Run the EICAR test in Verify to confirm scanning is active.

MariaDB io_uring warning or Redis memory overcommit warning: both are host-level configuration notices, not application errors. To silence the Redis warning, set vm.overcommit_memory = 1 in /etc/sysctl.conf on the host and apply it with sysctl -p. The MariaDB notice needs no action — it falls back to libaio on its own.

access-vpn returning 403 for a Tailscale client: see Traefik troubleshooting for the source-IP caveat when traffic arrives through Docker published ports.