Облачные диски удобны ровно до того момента, пока не упираешься в ограничение бесплатного объёма, в скорость отдачи или в вопрос, кто именно имеет доступ к загруженным файлам. Собственное файловое хранилище решает все три проблемы сразу, а 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
Первоначальный минимум действий после установки выглядит так:
- Перевести фоновые задачи в режим cron, поскольку в режиме AJAX они выполняются только во время обращения к интерфейсу;
- Задать каталог данных вне корневого каталога приложения, иначе при ошибке в настройках веб-сервера файлы могут быть выданы наружу;
- Включить ведение журнала обращений, без него разбор проблем превращается в угадывание;
- Создать отдельную учётную запись для ежедневного использования и оставить административную только для настроек;
- Проверить состояние командой 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, политику хранения пора ужесточить.
Измерить реальную производительность помогает простой тест: загрузка папки на тысячу файлов с последующим замером времени. Цифра до и после каждой настройки говорит больше, чем рассуждения о том, стало быстрее или нет.
Собственное хранилище требует внимания примерно час в месяц: проверить место, убедиться в создании копии, поставить обновления. Взамен получается объём, ограниченный только числом дисков, скорость, ограниченная только каналом, и контроль над доступом к содержимому.