# Verified compose for Seafile, tested 2026-10-16 by the SelfHostBench lab (3 runs; the figures are the median). # Lab host: AMD EPYC 9V45 96-Core Processor, 2 vCPU, 7.8 GiB RAM, x86_64, Docker 28.0.4. # Floating image tags below were replaced by the version resolved at test time; # the registry digest is in the comment on each image line. # Measurements: seafile.json (schema 3). # Companion env file: seafile.env (save it as .env next to this file). # Secrets are not published: the lab's values are public. In seafile.env: # INIT_SEAFILE_ADMIN_PASSWORD is commented out. Uncomment it and set your own value (the comment above it explains how). # INIT_SEAFILE_MYSQL_ROOT_PASSWORD is commented out. Uncomment it and set your own value (the comment above it explains how). # SEAFILE_MYSQL_DB_PASSWORD is commented out. Uncomment it and set your own value (the comment above it explains how). # JWT_PRIVATE_KEY is commented out. Uncomment it and set your own value (the comment above it explains how). # Seafile Community Edition 13.0 with MariaDB and Redis. # Follows the official Docker setup, https://manual.seafile.com/13.0/setup/setup_ce_by_docker/ (seafile-server.yml and .env of # that page, read 2026-10-16). Differences from it, all marked below: one file instead of four, no Caddy and no SeaDoc, named volumes # instead of /opt/... bind paths, a pinned image tag, required passwords, healthchecks on every service, a stricter Seafile healthcheck. # There is no TLS in this file. Put a reverse proxy in front before you expose it (see the article). name: seafile services: db: # MariaDB 10.11, the version the official file uses. Seafile needs a MySQL-compatible database (SQLite is not supported since 11.0). image: mariadb:10.11 # sha256:7db29378d4fdab73f8123bbc2b48905c90d1a4b00cf848b028f1e81e623257f2 (version 10.11.19, from label:org.opencontainers.image.version) restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: "${INIT_SEAFILE_MYSQL_ROOT_PASSWORD:?set INIT_SEAFILE_MYSQL_ROOT_PASSWORD in .env}" MYSQL_LOG_CONSOLE: "true" MARIADB_AUTO_UPGRADE: "1" volumes: # The Seafile databases (ccnet_db, seafile_db, seahub_db). Back them up with mariadb-dump. - db:/var/lib/mysql healthcheck: test: ["CMD", "/usr/local/bin/healthcheck.sh", "--connect", "--mariadbupgrade", "--innodb_initialized"] interval: 10s start_period: 30s timeout: 5s retries: 12 redis: # Since Seafile 13 Redis is the recommended cache (memcached is optional and no longer part of the official file). # No password and no published port: only containers on this Compose network can reach it. image: redis:8.10.2 # sha256:2c2dff791878316e3b083188be0d578423b24813cdda5de0e80ea4f7b3e6bfd6 (version 8.10.2, from exec) restart: unless-stopped command: - /bin/sh - -c - exec redis-server --requirepass "$$REDIS_PASSWORD" --save "" --appendonly no environment: REDIS_PASSWORD: "${REDIS_PASSWORD:-}" healthcheck: # Differs from the official file, which has no healthcheck here. test: ["CMD-SHELL", "redis-cli ping | grep -q PONG"] interval: 5s timeout: 3s retries: 12 seafile: # The Seafile server: seaf-server (file server, Go), Seahub (the web interface, Django) and the bundled nginx on port 80. image: seafileltd/seafile-mc:13.0.28 # sha256:b0c90832126bf432db908449f1bb450211e6bbf75a2193bab3399e2a9636eccf (version 13.0.28, from exec) restart: unless-stopped depends_on: db: condition: service_healthy redis: condition: service_healthy ports: # Localhost only. A reverse proxy on the host sends traffic here. The official file publishes nothing and puts Caddy in front. - "127.0.0.1:8080:80" environment: # Database. The first start creates the three databases and the "seafile" database user with the root password. SEAFILE_MYSQL_DB_HOST: db SEAFILE_MYSQL_DB_PORT: "3306" SEAFILE_MYSQL_DB_USER: seafile SEAFILE_MYSQL_DB_PASSWORD: "${SEAFILE_MYSQL_DB_PASSWORD:?set SEAFILE_MYSQL_DB_PASSWORD in .env}" INIT_SEAFILE_MYSQL_ROOT_PASSWORD: "${INIT_SEAFILE_MYSQL_ROOT_PASSWORD:?set INIT_SEAFILE_MYSQL_ROOT_PASSWORD in .env}" SEAFILE_MYSQL_DB_CCNET_DB_NAME: ccnet_db SEAFILE_MYSQL_DB_SEAFILE_DB_NAME: seafile_db SEAFILE_MYSQL_DB_SEAHUB_DB_NAME: seahub_db TIME_ZONE: "${TIME_ZONE:-Etc/UTC}" # First admin account. Used on the first start only; later changes here do nothing. # The official file has defaults (me@example.com / asecret); here both must be set so nobody runs with a public password. INIT_SEAFILE_ADMIN_EMAIL: "${INIT_SEAFILE_ADMIN_EMAIL:?set INIT_SEAFILE_ADMIN_EMAIL in .env}" INIT_SEAFILE_ADMIN_PASSWORD: "${INIT_SEAFILE_ADMIN_PASSWORD:?set INIT_SEAFILE_ADMIN_PASSWORD in .env}" # The address you type in the browser. Seafile builds its download and upload links from these two values. SEAFILE_SERVER_HOSTNAME: "${SEAFILE_SERVER_HOSTNAME:?set SEAFILE_SERVER_HOSTNAME in .env}" SEAFILE_SERVER_PROTOCOL: "${SEAFILE_SERVER_PROTOCOL:-http}" # At least 32 random characters. Seafile services use it to sign tokens for each other. JWT_PRIVATE_KEY: "${JWT_PRIVATE_KEY:?set JWT_PRIVATE_KEY in .env}" SITE_ROOT: / NON_ROOT: "false" SEAFILE_LOG_TO_STDOUT: "false" ENABLE_GO_FILESERVER: "true" # Redis for the cache. CACHE_PROVIDER: redis REDIS_HOST: redis REDIS_PORT: "6379" REDIS_PASSWORD: "${REDIS_PASSWORD:-}" # Optional components of the official file, all switched off here: SeaDoc (online documents; needs its own container), # the notification server, the metadata server and Seafile AI. ENABLE_SEADOC: "false" ENABLE_NOTIFICATION_SERVER: "false" ENABLE_SEAFILE_AI: "false" ENABLE_FACE_RECOGNITION: "false" volumes: # Libraries (file blocks), configuration (conf/) and logs. This is your data: back it up together with the database. - seafile-data:/shared healthcheck: # The official file only checks that port 80 answers. This one waits for the API (Seahub) and the file server (seaf-server) # behind the bundled nginx, which is when uploads work. test: ["CMD-SHELL", "curl -fsS http://localhost/api2/ping/ | grep -q pong && curl -fsS http://localhost/seafhttp/protocol-version | grep -q version"] interval: 10s timeout: 10s retries: 60 start_period: 30s volumes: db: seafile-data: