Seafile + OnlyOffice + Docker (Готовый стек для self-hosted альтернативы Nextcloud)

ala-a-din
Sat, 28 Feb 2026 15:44:48 +0000
Фото\Медиа сервисы

Делюсь полностью рабочими конфигурационными файлами, которые я выстрадал и проверил в бою.
Это полноценная замена Nextcloud на базе Seafile — более лёгкая, быстрая и с отличной синхронизацией. В комплекте:

Seafile (Community Edition 13.0.18) — само хранилище, работает как часы.

OnlyOffice (8.1.0.1) — онлайн-редактор документов, таблиц и презентаций, идеально встраивается в Seafile.

SeaDoc — редактор документов (ещё одна фича Seafile).

Notification Server — для уведомлений и мониторинга (ping-эндпоинт).

Всё завернуто в Docker Compose, все версии образов зафиксированы (исключить внезапные обновы).
Настройки вынесены в единый .env — легко править под себя.

Как использовать

1. Подготовьте чистую VM (Debian 12) с достаточным дисковым пространством (минимум 20–30 ГБ, или столько, сколько вам нужно облачного пространства).

2. Установите Docker и Docker Compose (если ещё не сделано).

3. Создайте директорию ( /opt/seafile) и поместите в неё все файлы:

.env (переименуйте, подставьте свои данные)

seafile-server.yml

seadoc.yml

onlyoffice.yml

notification-server.yml

4. Отредактируйте .env:

Замените your.domain.ru на ваш реальный домен (например, drive.example.com).

Сгенерируйте надёжные пароли для JWT_PRIVATE_KEY, SEAFILE_MYSQL_DB_PASSWORD, REDIS_PASSWORD, INIT_SEAFILE_MYSQL_ROOT_PASSWORD, ONLYOFFICE_JWT_SECRET.
Можно использовать команду (в терминале):
openssl rand -base64 32 | tr -dc 'a-zA-Z' | head -c 32; echo

Укажите ваш email и пароль администратора Seafile (INIT_SEAFILE_ADMIN_EMAIL, INIT_SEAFILE_ADMIN_PASSWORD).

Если у вас другой часовой пояс, измените TIME_ZONE.

5. Запустите стек:

cd /opt/seafile
docker compose up -d

6. Проверьте логи и доступность сервисов:

Seafile: http://ваш-ip:8080 (после настройки Traefik будет доступен по домену)

OnlyOffice: http://ваш-ip:6233/welcome/

Notification Server: http://ваш-ip:8083/ping (должен ответить {"ret":"pong"})

Доступ из интернета (опционально)

Я использую Traefik как внешний прокси с автоматическими SSL-сертификатами.
Вы можете адаптировать конфигурацию под свой прокси (nginx, Caddy и т.д.).
Для Traefik пример конфига:

http:
  routers:
    seafile-main:
      rule: "Host(`seafile.example.com`) && !PathPrefix(`/notification`)"
      entryPoints: ["websecure"]
      tls:
        certResolver: letsencrypt
        options: modern@file
      middlewares:
        - chain-public@file
        - seafile-headers@file
      service: seafile-main

    seafile-notification:
      rule: "Host(`seafile.example.com`) && PathPrefix(`/notification`)"
      entryPoints: ["websecure"]
      tls:
        certResolver: letsencrypt
        options: modern@file
      middlewares:
        - chain-public@file
        - notification-headers@file
      service: seafile-notification

    onlyoffice:
      rule: "Host(`office.example.com`)"
      entryPoints: ["websecure"]
      tls:
        certResolver: letsencrypt
        options: modern@file
      middlewares:
        - chain-public@file
        - onlyoffice-headers@file
      service: onlyoffice

  services:
    seafile-main:
      loadBalancer:
        servers:
          - url: "http://<IP_вашего_сервера_Seafile>:8080"

    seafile-notification:
      loadBalancer:
        servers:
          - url: "http://<IP_вашего_сервера_Seafile>:8083"
        healthCheck:
          path: /ping
          interval: 10s
          timeout: 3s

    onlyoffice:
      loadBalancer:
        passHostHeader: true
        servers:
          - url: "http://<IP_вашего_сервера_Seafile>:6233"

  middlewares:
    seafile-headers:
      headers:
        customRequestHeaders:
          X-Forwarded-Proto: "https"
          X-Forwarded-Host: "seafile.example.com"
        customResponseHeaders:
          X-Frame-Options: "SAMEORIGIN"
          Content-Security-Policy: "frame-ancestors 'self' https://seafile.example.com"

    notification-headers:
      headers:
        customRequestHeaders:
          X-Forwarded-Proto: "https"
          X-Forwarded-Host: "seafile.example.com"

    onlyoffice-headers:
      headers:
        customRequestHeaders:
          X-Forwarded-Proto: "https"
          X-Forwarded-Host: "office.example.com"
        customResponseHeaders:
          X-Frame-Options: ""
          Content-Security-Policy: "frame-ancestors 'self' https://seafile.example.com"
        stsSeconds: 31536000
        stsIncludeSubdomains: true
        stsPreload: true
        forceSTSHeader: true
        browserXssFilter: true
        contentTypeNosniff: true
        referrerPolicy: "strict-origin-when-cross-origin"
        permissionsPolicy: "geolocation=(self), camera=(self), microphone=(self)"

Пояснения для сообщества

Замените seafile.example.com и office.example.com на ваши реальные домены.

Вместо <IP_вашего_сервера_Seafile> укажите локальный IP машины, на которой запущен Seafile (например, 192.168.1.100).

chain-public@file – это ссылка на цепочку middleware, которая обычно включает CrowdSec, rate limit и базовые заголовки безопасности. Вы можете определить её в отдельном файле или заменить на свои middleware.

modern@file – TLS-опции, например:

tls:
  options:
    modern:
      minVersion: VersionTLS12

Middleware seafile-headers, notification-headers и onlyoffice-headers уже описаны здесь же – они добавляют необходимые заголовки для корректной работы Seafile и OnlyOffice (в частности, для работы OnlyOffice в iframe).

Состав файлов

Файл | Назначение
.env | Все настройки (пароли, пути, домены)
seafile-server.yml | Основной стек: MariaDB, Redis, Seafile
seadoc.yml | Сервис SeaDoc
onlyoffice.yml | OnlyOffice Document Server
notification-server.yml | Сервер уведомлений (нужен для мониторинга)

Важно

В файлах нет чувствительных данных — только шаблоны. Все секреты вы задаёте сами в .env.

Версии образов жёстко зафиксированы (никаких latest), чтобы ничего не сломалось при обновлениях.

Если вам нужен антивирус (ClamAV) — официальная интеграция в Docker-версии Seafile не работает (отсутствует clamdscan в контейнере). Я отказался от неё, но вы можете попробовать самостоятельно.

Версия от 28.02.2026, проверено в работе.

notification-server.yml

services:
  notification-server:
    image: ${NOTIFICATION_SERVER_IMAGE}
    container_name: notification-server
    restart: unless-stopped
    volumes:
      - ${SEAFILE_VOLUME}/seafile/logs:/shared/seafile/logs
    ports:
      - "8083:8083"
    environment:
      - SEAFILE_MYSQL_DB_HOST=${SEAFILE_MYSQL_DB_HOST}
      - SEAFILE_MYSQL_DB_PORT=${SEAFILE_MYSQL_DB_PORT}
      - SEAFILE_MYSQL_DB_USER=${SEAFILE_MYSQL_DB_USER}
      - SEAFILE_MYSQL_DB_PASSWORD=${SEAFILE_MYSQL_DB_PASSWORD:?Variable is not set or empty}
      - SEAFILE_MYSQL_DB_CCNET_DB_NAME=${SEAFILE_MYSQL_DB_CCNET_DB_NAME}
      - SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=${SEAFILE_MYSQL_DB_SEAFILE_DB_NAME}
      - JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY:?Variable is not set or empty}
      - SEAFILE_LOG_TO_STDOUT=${SEAFILE_LOG_TO_STDOUT}
      - NOTIFICATION_SERVER_LOG_LEVEL=${NOTIFICATION_SERVER_LOG_LEVEL}
      - NOTIFICATION_SERVER_URL=${NOTIFICATION_SERVER_URL}
      - INNER_NOTIFICATION_SERVER_URL=${INNER_NOTIFICATION_SERVER_URL}
    depends_on:
      db:
        condition: service_healthy
      seafile:
        condition: service_healthy
    networks:
      - seafile-net

networks:
  seafile-net:
    name: seafile-net
artidev@debian-external:/opt/seafile$ cat onlyoffice.yml
services:
  onlyoffice:
    image: ${ONLYOFFICE_IMAGE}
    restart: unless-stopped
    container_name: seafile-onlyoffice
    ports:
      - "${ONLYOFFICE_PORT}:80"
    environment:
      - JWT_ENABLED=true
      - JWT_SECRET=${ONLYOFFICE_JWT_SECRET:?Variable is not set or empty}
      - PROXY_TRUSTED=true
      - PROXY_PROTOCOL=https
      - PROXY_HOST=office.yourdomain.ru # заменить на свой реальный
      - PROXY_PORT=443
    volumes:
      - ${ONLYOFFICE_VOLUME}/logs:/var/log/onlyoffice
      - ${ONLYOFFICE_VOLUME}/data:/var/www/onlyoffice/Data
      - ${ONLYOFFICE_VOLUME}/lib:/var/lib/onlyoffice
    networks:
      - seafile-net

networks:
  seafile-net:
    name: seafile-net

seadoc.yml

services:
  seadoc:
    image: ${SEADOC_IMAGE}
    container_name: seadoc
    restart: unless-stopped
    volumes:
      - ${SEADOC_VOLUME}:/shared
    # ports:
    #   - "80:80"
    environment:
      - DB_HOST=${SEAFILE_MYSQL_DB_HOST}
      - DB_PORT=${SEAFILE_MYSQL_DB_PORT}
      - DB_USER=${SEAFILE_MYSQL_DB_USER}
      - DB_PASSWORD=${SEAFILE_MYSQL_DB_PASSWORD:?Variable is not set or empty}
      - DB_NAME=${SEADOC_MYSQL_DB_NAME}
      - TIME_ZONE=${TIME_ZONE}
      - JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY:?Variable is not set or empty}
      - NON_ROOT=${NON_ROOT}
      - SEAHUB_SERVICE_URL=${SEAFILE_SERVICE_URL}
    depends_on:
      db:
        condition: service_healthy
    networks:
      - seafile-net

networks:
  seafile-net:
    name: seafile-net

seafile-server.yml

services:
  db:
    image: ${SEAFILE_DB_IMAGE}
    container_name: seafile-mysql
    restart: unless-stopped
    environment:
      - MYSQL_ROOT_PASSWORD=${INIT_SEAFILE_MYSQL_ROOT_PASSWORD}
      - MYSQL_LOG_CONSOLE=true
      - MARIADB_AUTO_UPGRADE=1
    volumes:
      - "${SEAFILE_MYSQL_VOLUME}:/var/lib/mysql"
    networks:
      - seafile-net
    healthcheck:
      test:
        [
          "CMD",
          "/usr/local/bin/healthcheck.sh",
          "--connect",
          "--mariadbupgrade",
          "--innodb_initialized",
        ]
      interval: 20s
      start_period: 30s
      timeout: 5s
      retries: 10

  redis:
    image: ${SEAFILE_REDIS_IMAGE}
    container_name: seafile-redis
    restart: unless-stopped
    command:
      - /bin/sh
      - -c
      - redis-server --requirepass "$$REDIS_PASSWORD"
    environment:
      - REDIS_PASSWORD=${REDIS_PASSWORD}
    networks:
      - seafile-net

  seafile:
    image: ${SEAFILE_IMAGE}
    container_name: seafile
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ${SEAFILE_VOLUME}:/shared
    environment:
      - SEAFILE_MYSQL_DB_HOST=${SEAFILE_MYSQL_DB_HOST}
      - SEAFILE_MYSQL_DB_PORT=${SEAFILE_MYSQL_DB_PORT}
      - SEAFILE_MYSQL_DB_USER=${SEAFILE_MYSQL_DB_USER}
      - SEAFILE_MYSQL_DB_PASSWORD=${SEAFILE_MYSQL_DB_PASSWORD:?Variable is not set or empty}
      - INIT_SEAFILE_MYSQL_ROOT_PASSWORD=${INIT_SEAFILE_MYSQL_ROOT_PASSWORD}
      - SEAFILE_MYSQL_DB_CCNET_DB_NAME=${SEAFILE_MYSQL_DB_CCNET_DB_NAME}
      - SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=${SEAFILE_MYSQL_DB_SEAFILE_DB_NAME}
      - SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=${SEAFILE_MYSQL_DB_SEAHUB_DB_NAME}
      - TIME_ZONE=${TIME_ZONE}
      - INIT_SEAFILE_ADMIN_EMAIL=${INIT_SEAFILE_ADMIN_EMAIL}
      - INIT_SEAFILE_ADMIN_PASSWORD=${INIT_SEAFILE_ADMIN_PASSWORD}
      - SEAFILE_SERVER_HOSTNAME=${SEAFILE_SERVER_HOSTNAME:?Variable is not set or empty}
      - SEAFILE_SERVER_PROTOCOL=${SEAFILE_SERVER_PROTOCOL}
      - SITE_ROOT=${SITE_ROOT}
      - NON_ROOT=${NON_ROOT}
      - JWT_PRIVATE_KEY=${JWT_PRIVATE_KEY:?Variable is not set or empty}
      - SEAFILE_LOG_TO_STDOUT=${SEAFILE_LOG_TO_STDOUT}
      - ENABLE_GO_FILESERVER=${ENABLE_GO_FILESERVER}
      - ENABLE_SEADOC=${ENABLE_SEADOC}
      - SEADOC_SERVER_URL=${SEAFILE_SERVER_PROTOCOL}://${SEAFILE_SERVER_HOSTNAME}/sdoc-server
      - CACHE_PROVIDER=${CACHE_PROVIDER}
      - REDIS_HOST=${REDIS_HOST}
      - REDIS_PORT=${REDIS_PORT}
      - REDIS_PASSWORD=${REDIS_PASSWORD}
      - MEMCACHED_HOST=${MEMCACHED_HOST}
      - MEMCACHED_PORT=${MEMCACHED_PORT}
      - ENABLE_NOTIFICATION_SERVER=${ENABLE_NOTIFICATION_SERVER}
      - INNER_NOTIFICATION_SERVER_URL=${INNER_NOTIFICATION_SERVER_URL}
      - NOTIFICATION_SERVER_URL=${NOTIFICATION_SERVER_URL}
      - ENABLE_SEAFILE_AI=${ENABLE_SEAFILE_AI}
      - SEAFILE_AI_SERVER_URL=${SEAFILE_AI_SERVER_URL}
      - SEAFILE_AI_SECRET_KEY=${JWT_PRIVATE_KEY}
      - MD_FILE_COUNT_LIMIT=${MD_FILE_COUNT_LIMIT}
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:80 || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - seafile-net

networks:
  seafile-net:
    name: seafile-net

.env

COMPOSE_PATH_SEPARATOR=,
COMPOSE_FILE=seafile-server.yml,seadoc.yml,onlyoffice.yml,notification-server.yml
SEAFILE_IMAGE=seafileltd/seafile-mc:13.0.18
SEAFILE_DB_IMAGE=mariadb:10.11.16
SEAFILE_REDIS_IMAGE=redis:8.6.1
SEADOC_IMAGE=seafileltd/sdoc-server:2.0.9
NOTIFICATION_SERVER_IMAGE=seafileltd/notification-server:13.0.10
ONLYOFFICE_IMAGE=onlyoffice/documentserver:8.1.0.1

BASIC_STORAGE_PATH=/opt
SEAFILE_VOLUME=$BASIC_STORAGE_PATH/seafile-data
SEAFILE_MYSQL_VOLUME=$BASIC_STORAGE_PATH/seafile-mysql/db
SEADOC_VOLUME=$BASIC_STORAGE_PATH/seadoc-data
ONLYOFFICE_VOLUME=$BASIC_STORAGE_PATH/onlyoffice

SEAFILE_SERVER_HOSTNAME=your.domain.ru
SEAFILE_SERVER_PROTOCOL=https
TIME_ZONE=Europe/Moscow
JWT_PRIVATE_KEY=password must be at least 32 characters long
SEAFILE_MYSQL_DB_HOST=db
SEAFILE_MYSQL_DB_PORT=3306
SEAFILE_MYSQL_DB_USER=seafile
SEAFILE_MYSQL_DB_PASSWORD=password
SEAFILE_MYSQL_DB_CCNET_DB_NAME=ccnet_db
SEAFILE_MYSQL_DB_SEAFILE_DB_NAME=seafile_db
SEAFILE_MYSQL_DB_SEAHUB_DB_NAME=seahub_db
CACHE_PROVIDER=redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=password
INIT_SEAFILE_MYSQL_ROOT_PASSWORD=password
INIT_SEAFILE_ADMIN_EMAIL=your e-mail
INIT_SEAFILE_ADMIN_PASSWORD=e-mail application password
ENABLE_SEADOC=true
ENABLE_NOTIFICATION_SERVER=true
NOTIFICATION_SERVER_LOG_LEVEL=info
NOTIFICATION_SERVER_URL=https://your.domain.ru/notification
INNER_NOTIFICATION_SERVER_URL=https://notification-server:8083
ENABLE_SEAFILE_AI=false
MD_FILE_COUNT_LIMIT=100000
ONLYOFFICE_PORT=6233
ONLYOFFICE_JWT_SECRET=password must be at least 32 characters long

SEADOC_MYSQL_DB_NAME=seahub_db
SEAFILE_SERVICE_URL=https://seafile
SEAFILE_LOG_TO_STDOUT=false
ENABLE_GO_FILESERVER=true
SITE_ROOT=/
NON_ROOT=false
SEAFILE_AI_SERVER_URL=https://seafile-ai:8888
MEMCACHED_HOST=
MEMCACHED_PORT=

Команды для генерации случайного набора символов:

openssl rand -base64 32 | tr -dc 'a-zA-Z' | head -c 32; echo

Если нужно больше/меньше символов:
Для 16 букв:
openssl rand -base64 16 | tr -dc 'a-zA-Z' | head -c 16; echo

Для 24 букв:
openssl rand -base64 18 | tr -dc 'a-zA-Z' | head -c 24; echo

Для 64 букв:
openssl rand -base64 48 | tr -dc 'a-zA-Z' | head -c 64; echo

После развертывания стека, нужно отредактировать системный файл конфигурации: seahub_settings.py

Пример моего рабочего seahub_settings.py имеет следующий вид.

# -*- coding: utf-8 -*-
SECRET_KEY = будет сгененрирован в процессе установки
TIME_ZONE = 'Europe/Moscow'

EMAIL_USE_SSL = True
EMAIL_HOST = ''        # smpt server
EMAIL_HOST_USER = ''    # username and domain
EMAIL_HOST_PASSWORD = ''    # password
EMAIL_PORT =
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
SERVER_EMAIL = EMAIL_HOST_USER

ENABLE_ONLYOFFICE = True
ONLYOFFICE_APIJS_URL = 'https://office.yourdomain.ru/web-apps/apps/api/documents/api.js'
ONLYOFFICE_JWT_SECRET = '' # должен совпадать с .env
ONLYOFFICE_FILE_EXTENSION = ('doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'odt', 'fodt', 'odp', 'fodp', 'ods', 'fods', 'ppsx', 'pps', 'csv')
ONLYOFFICE_EDIT_FILE_EXTENSION = ('docx', 'pptx', 'xls', 'csv')
OFFICE_PREVIEW_MAX_SIZE = 30 * 1024 * 1024
ONLYOFFICE_FORCE_SAVE = True

SERVICE_URL = 'https://drive.yourdomain.ru'
FILE_SERVER_ROOT = 'https://drive.yourdomain.ru/seafhttp'

Как использовать

Отредактируйте содержимое файла, который находится в папке conf вашего Seafile (обычно /opt/seafile-data/seafile/conf/) с помощью команды:

sudo /opt/seafile-data/seafile/conf/seahub_settings.py

Замените все yourdomain.ru на свой реальный домен.

Перезапустите контейнер Seafile, чтобы изменения вступили в силу:

docker restart seafile

Источник: Setup community edition - Seafile Admin Manual

20 сообщений - 7 участников

Читать всю тему