Облачные диски удобны ровно до того момента, пока не упираешься в ограничение бесплатного объёма, в скорость отдачи или в вопрос, кто именно имеет доступ к загруженным файлам. Собственное файловое хранилище решает все три проблемы сразу, а Nextcloud закрывает ещё и синхронизацию между устройствами, версионирование и совместную работу. Разбираем установку в контейнерах с отдельной базой данных и кэшем, первоначальную настройку и подключение настольных и мобильных клиентов.

Зачем своё хранилище и чем оно отличается от подписки на диск

Разница не в цене, хотя и в ней тоже. Аренда двух терабайт у крупного провайдера обходится в несколько сотен рублей ежемесячно, то есть в сумму, за которую раз в год можно купить ещё один накопитель для домашнего сервера.

Главное отличие в управляемости. Скорость отдачи упирается только в канал и в диски, а не в тарифный план. Объём наращивается добавлением накопителя, а не переходом на следующую ступень подписки. Доступ к файлам из домашней сети идёт на скорости локальной сети, что для резервных копий и видеоархива принципиально.

Второе отличие в составе возможностей. Nextcloud это не только файлы. В нём есть календарь, контакты, задачи, заметки и клиентская синхронизация всего перечисленного. Один сервер закрывает то, за что в облачной модели берут отдельные деньги по разным подпискам.

Плата за это предсказуемая: за работоспособность отвечаешь сам. Обновления, резервные копии, свободное место и сертификаты ложатся на владельца, и пренебрежение любым из пунктов рано или поздно приводит к потере данных.

Требования к железу и выбор между PostgreSQL и MariaDB

Актуальная ветка сервера, Nextcloud 35, поддерживает PHP версий 8.3, 8.4 и 8.5, причём 8.5 указана как рекомендуемая. По базам данных поддерживаются MariaDB от 10.11 до 12.3 и PostgreSQL от 14 до 18, с рекомендацией в сторону MariaDB 11.8 и PostgreSQL 18.

По памяти требования скромные на бумаге и совсем другие на практике. Формально достаточно 128 МБ на процесс, рекомендовано 512 МБ, а встроенному механизму обновлений нужно не менее 256 МБ. Реально для семьи из трёх-четырёх человек с синхронизацией и индексацией файлов стоит закладывать 4 ГБ оперативной памяти и два ядра процессора. С 2 ГБ система работает, но фоновая индексация при загрузке большой папки заметно тормозит интерфейс.

Выбор базы данных для домашнего сценария сводится к следующему. PostgreSQL лучше справляется с крупными таблицами и даёт более предсказуемое поведение при конкурентных операциях, а MariaDB проще в обслуживании и чуть экономнее по памяти. Обе поддерживаются полноценно, и ошибка при выборе между ними куда менее критична, чем отсутствие резервных копий.

Отдельно про накопитель. Файлы и базу лучше разнести по разным дискам, а под базу отдать твердотельный накопитель. Операций с мелкими записями у платформы много, и именно они определяют отзывчивость интерфейса.

Полный compose-файл с базой данных, кэшем Redis и фоновыми задачами

Ставить всё в один контейнер с внутренней базой SQLite вариант для теста на полчаса. Для постоянного использования нужны четыре службы: приложение, база, кэш и планировщик фоновых задач.

services:
  db:
    image: postgres:18
    container_name: nc-db
    restart: unless-stopped
    environment:
      POSTGRES_DB: nextcloud
      POSTGRES_USER: nextcloud
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - ./db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U nextcloud"]
      interval: 10s
      retries: 5

  redis:
    image: redis:7-alpine
    container_name: nc-redis
    restart: unless-stopped
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru

  app:
    image: nextcloud:35-apache
    container_name: nc-app
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    environment:
      POSTGRES_HOST: db
      POSTGRES_DB: nextcloud
      POSTGRES_USER: nextcloud
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      REDIS_HOST: redis
      NEXTCLOUD_TRUSTED_DOMAINS: cloud.local
      PHP_MEMORY_LIMIT: 1G
      PHP_UPLOAD_LIMIT: 10G
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - ./app:/var/www/html
      - ./data:/var/www/html/data

  cron:
    image: nextcloud:35-apache
    container_name: nc-cron
    restart: unless-stopped
    depends_on:
      - app
    volumes:
      - ./app:/var/www/html
    entrypoint: /cron.sh

Пароль базы не вписан в файл, а приходит из переменных окружения. Рядом с compose-файлом лежит файл .env с правами 600:

DB_PASSWORD=длинный_случайный_пароль

Ограничение maxmemory у кэша обязательно. Без него Redis со временем занимает всю свободную память машины, и политика вытеснения allkeys-lru здесь уместна, поскольку кэш по определению восстанавливаем.

Порт привязан к локальному адресу, потому что наружу приложение должно смотреть через обратный прокси с шифрованием, а не напрямую.

Первоначальная настройка через веб и обязательные правки конфигурации

Первый запуск:

docker compose up -d
docker compose logs -f app

Установка занимает несколько минут, в течение которых создаётся структура базы. После этого страница на локальном адресе предлагает мастер первоначальной настройки, где задаётся учётная запись администратора и проверяется подключение к базе.

Дальше идут правки, без которых система будет работать, но с постоянными предупреждениями. Проверка состояния выполняется командой:

docker compose exec app php occ status

Фоновые задачи по умолчанию выставлены в режим AJAX, при котором они выполняются только во время обращения к интерфейсу. Для сервера, который должен индексировать файлы и рассылать уведомления постоянно, нужен режим cron, который и обеспечивает четвёртая служба в compose-файле:

docker compose exec app php occ background:cron

Первоначальный минимум действий после установки выглядит так:

  1. Перевести фоновые задачи в режим cron, поскольку в режиме AJAX они выполняются только во время обращения к интерфейсу;
  2. Задать каталог данных вне корневого каталога приложения, иначе при ошибке в настройках веб-сервера файлы могут быть выданы наружу;
  3. Включить ведение журнала обращений, без него разбор проблем превращается в угадывание;
  4. Создать отдельную учётную запись для ежедневного использования и оставить административную только для настроек;
  5. Проверить состояние командой occ status и убрать предупреждения из раздела администрирования до того, как они накопятся.

Отдельная тема, доступ извне домашней сети. Напрямую выпускать контейнер в интернет нельзя, нужен обратный прокси с шифрованием и корректной передачей заголовков. Платформа должна знать, по какому адресу к ней обращаются, иначе ссылки в интерфейсе будут вести на внутренний порт. Задаётся это переменной доверенных доменов, а в конфигурации проверяется так:

docker compose exec app php occ config:system:get trusted_domains

Если доступ извне всё же нужен, публикуют только порт шифрованного слушателя веб-сервера, остальные порты маршрутизатора оставляют закрытыми. Доступ ограничивают списком доверенных адресов, а для разовых административных действий пользуются консолью провайдера.

Полезная команда для осмотра общего состояния:

docker compose exec app php occ config:list system

Подключение настольного и мобильного клиентов с выборочной синхронизацией

Настольный клиент работает на Windows начиная с версии 10, на macOS начиная с Monterey и на любом 64-битном Linux через официальный пакет в формате AppImage. Мобильное приложение для файлов требует Android 9 и новее или iOS 17 и новее.

Подключение сводится к вводу адреса сервера, имени учётной записи и пароля. Но есть деталь, которую новички упускают: для входа стоит использовать не основной пароль, а отдельный пароль приложения. Создаётся он в разделе безопасности профиля и позволяет отозвать доступ к одному устройству, не меняя пароль везде.

Выборочная синхронизация настраивается в параметрах папок настольного клиента. Синхронизировать весь сервер целиком на ноутбук с небольшим диском не нужно, достаточно выбрать рабочие каталоги. Всё остальное остаётся доступным по запросу.

Для смартфонов полезно отключить автоматическую загрузку всего подряд и оставить только нужные каталоги, иначе мобильный трафик уходит незаметно. Отдельно стоит проверить, что клиент не пытается синхронизировать каталог с большим количеством мелких файлов, например кэш программ, поскольку именно такие папки создают основную нагрузку на сервер.

Проверить, что синхронизация действительно идёт, можно по журналу сервера:

docker compose logs --tail 50 app | grep -i 'sync\|dav'

Резервное копирование данных и базы с проверкой восстановления

Резервная копия файлов без копии базы бесполезна, и наоборот. Копировать нужно три вещи: каталог с файлами, каталог с данными базы и файл конфигурации платформы.

База выгружается в согласованном состоянии одной командой:

docker compose exec -T db pg_dump -U nextcloud nextcloud | gzip > backup/db-$(date +%F).sql.gz

Файлы копируются отдельно, и здесь есть тонкость. Копировать их при работающей индексации значит получить снимок, где часть записей относится к одному моменту времени, а часть к другому. Для домашнего хранилища это обычно допустимо, но надёжнее включить режим обслуживания на время копирования:

docker compose exec app php occ maintenance:mode --on
tar czf backup/files-$(date +%F).tar.gz -C ./data .
docker compose exec app php occ maintenance:mode --off

Полный скрипт с ротацией в тридцать дней:

#!/usr/bin/env bash
set -euo pipefail
cd /opt/nextcloud
mkdir -p backup
docker compose exec -T db pg_dump -U nextcloud nextcloud | gzip > backup/db-$(date +%F).sql.gz
tar czf backup/files-$(date +%F).tar.gz -C ./data .
cp app/config/config.php backup/config-$(date +%F).php
find backup -mtime +30 -delete

Копию обязательно держать на другом носителе. Хранение архива на том же диске, где лежат исходные файлы, это не резервное копирование, а дублирование, и от выхода накопителя из строя оно не защищает.

Проверка восстановления важнее самого резервного копирования. Раз в несколько месяцев стоит развернуть копию в отдельный каталог и убедиться, что файлы читаются, а база поднимается. Копия, которую ни разу не восстанавливали, это гипотеза, а не гарантия.

Производительность на большой коллекции файлов и типовые ошибки

С ростом числа файлов система начинает работать медленнее, и почти всегда по одной из четырёх причин.

Первая, отсутствие кэша. Если Redis не подключён или отвалился, каждый запрос к списку файлов идёт в базу. Проверяется наличием записи memcache.distributed в выводе config:list system.

Вторая, фоновые задачи в режиме AJAX. Индексация и очистка старых версий просто не выполняются, и база разрастается. Лечится переводом в режим cron.

Третья, индексация свежезагруженного архива. Загрузка папки на сотни тысяч файлов запускает длительный процесс разбора, и в это время интерфейс отвечает неохотно. Такую загрузку лучше делать ночью и через веб-интерфейс, а не через клиент синхронизации.

Четвёртая, файловая система. Хранение большого количества мелких файлов на сетевом диске даёт многократное падение скорости по сравнению с локальным накопителем. Если без сетевого хранилища не обойтись, файлы стоит держать в контейнерном томе на локальном диске, а наружу отдавать уже готовые архивы.

Пятая причина, о которой забывают чаще всего, это версии файлов и корзина. Платформа хранит историю изменений и удалённые файлы, и при активной работе с документами эти данные могут занимать больше места, чем сами файлы. Настройка политики хранения задаётся в конфигурации и ограничивает срок жизни старых версий.

Полезно раз в месяц смотреть, что именно заняло объём:

docker compose exec app php occ files:scan --all
du -sh data/*/files data/*/versions data/*/uploads 2>/dev/null | sort -h | tail

Первая команда пересчитывает индекс, вторая показывает распределение места по каталогам пользователей. Если каталог versions сопоставим по размеру с files, политику хранения пора ужесточить.

Измерить реальную производительность помогает простой тест: загрузка папки на тысячу файлов с последующим замером времени. Цифра до и после каждой настройки говорит больше, чем рассуждения о том, стало быстрее или нет.

Собственное хранилище требует внимания примерно час в месяц: проверить место, убедиться в создании копии, поставить обновления. Взамен получается объём, ограниченный только числом дисков, скорость, ограниченная только каналом, и контроль над доступом к содержимому.