Top.Mail.Ru

Nextcloud на VPS: своё облако через Docker Compose

2
Nextcloud на VPS: своё облако через Docker Compose

Nextcloud — это собственное облако: файлы с синхронизацией на телефон и компьютер, календарь, контакты, общий доступ по ссылке. На VPS его поднимают через Docker Compose из пяти контейнеров (подробности ниже): Nextcloud, база PostgreSQL, кэш Redis, контейнер для фоновых задач и Caddy, который выпускает HTTPS-сертификат. В статье — готовые файлы, настройка, после которой пропадают предупреждения в панели администратора, резервная копия и обновление. Мы поставили Nextcloud 35.0.2 (Hub 26 Summer) на сервер с Ubuntu 24.04, и все выводы сняты с него.

Что получится и сколько нужно памяти и диска

Вы получите облако на своём домене, с шифрованным соединением и собственной базой данных. Файлы хранятся на диске вашего сервера, а не у стороннего сервиса.

Замеры на свежей установке без пользователей, в простое:

Контейнер Память
nextcloud 208 МБ
db (PostgreSQL) 69 МБ
redis 12 МБ
caddy 14 МБ
cron 2 МБ

Всего около 300 МБ. С работающими пользователями, загрузкой фотографий и предпросмотрами цифры вырастут, поэтому берите сервер с запасом. В документации Nextcloud на каждый процесс PHP (он обслуживает один запрос) рекомендуют не меньше 512 МБ, а процессов бывает несколько. Ориентир, не гарантия: для одного-двух пользователей хватит 2 ГБ, для семьи или небольшой команды надёжнее 4 ГБ. В линейке VPS UFO.Hosting это конфигурации с 2 и 4 ГБ памяти, актуальные параметры смотрите на странице тарифов.

Теперь диск. Образы занимают около 2,7 ГБ (Nextcloud 2,13 ГБ, PostgreSQL 424 МБ, остальное мелочь). После установки том Nextcloud занимал ещё 914 МБ, том базы 71 МБ, а дальше добавляются ваши файлы.

Что подготовить

  • VPS с Docker. Установка описана в статье про Docker и Compose.
  • Домен или поддомен, например cloud.example.com, с A-записью на IP сервера (как это сделать, написано в статье «Как прописать DNS для сервера»). Запись должна заработать до запуска: Caddy пойдёт за сертификатом сразу же. Проверить можно командой getent hosts cloud.example.com.
  • Свободные порты 80 и 443. Если включён ufw, откройте их: sudo ufw allow 80/tcp и sudo ufw allow 443/tcp. Для опубликованных портов Docker это формальность, он откроет их и сам, но правила пригодятся, если схема изменится.

Если порты 80 и 443 уже занимает другая программа (nginx, панель), Caddy из этой статьи не стартует. Docker ответит address already in use, если порт держит программа на сервере, или port is already allocated, если другой контейнер. Тогда либо освободите порты, либо пустите Nextcloud за уже работающим веб-сервером: такая схема описана в статье про обратный прокси nginx.

Зачем пять контейнеров

  • nextcloud — сам сервис. Берём вариант образа apache: в нём уже есть веб-сервер.
  • db — PostgreSQL. Nextcloud умеет работать и со встроенной SQLite (база в одном файле), но по документации она годится только для проверки и совсем маленьких установок. Сразу берите полноценную базу: переносить данные потом сложнее.
  • redis — быстрый кэш. Он нужен, чтобы блокировки файлов не шли через базу данных: блокировка не даёт двум клиентам одновременно записать один файл. Образ Nextcloud подхватывает Redis сам, если указать его адрес.
  • cron — запускает фоновые задачи Nextcloud: очистку, индексацию. Образ тот же, меняется только команда запуска.
  • caddy — обратный прокси: программа, которая принимает запросы из интернета на портах 80 и 443, передаёт их Nextcloud, а сертификат получает и продлевает сама.

Файлы проекта

mkdir ~/nextcloud && cd ~/nextcloud

Файл .env. Это набор переменных, которые Compose подставляет в compose.yaml. Здесь лежат домен и пароли, пароли сгенерируем случайные:

cat > .env <<EOF
DOMAIN=cloud.example.com
POSTGRES_PASSWORD=$(openssl rand -hex 24)
NEXTCLOUD_ADMIN_USER=admin
NEXTCLOUD_ADMIN_PASSWORD=$(openssl rand -hex 12)
EOF
chmod 600 .env

Замените cloud.example.com на свой домен. Пароль администратора записан в этом файле, с ним вы войдёте в Nextcloud: посмотреть его можно командой grep ADMIN_PASSWORD .env. После первого входа смените его на свой в личных настройках и сохраните в менеджере паролей.

Файл Caddyfile (nano Caddyfile):

{$DOMAIN} {
	log
	header Strict-Transport-Security "max-age=15552000; includeSubDomains"
	redir /.well-known/carddav /remote.php/dav 301
	redir /.well-known/caldav /remote.php/dav 301
	reverse_proxy nextcloud:80
}

Что делают строки. log включает журнал запросов: без него Caddy их не записывает, и вы не увидите, кто и откуда обращается к серверу. Заголовок Strict-Transport-Security (HSTS) требует от браузера полгода открывать сайт только по HTTPS. Браузер запомнит это до конца срока, поэтому включайте заголовок, когда HTTPS у вас уже работает. Две строки redir нужны приложениям календаря и контактов на телефоне: они ищут сервер по стандартным адресам /.well-known/caldav и /.well-known/carddav, а redir перенаправляет эти запросы на /remote.php/dav. Документация Nextcloud советует делать такие перенаправления на стороне прокси. Последняя строка передаёт всё остальное в контейнер Nextcloud.

Файл compose.yaml (nano compose.yaml):

services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    environment:
      - DOMAIN
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
    networks:
      - internal
    depends_on:
      - nextcloud

  nextcloud:
    image: nextcloud:35-apache
    restart: unless-stopped
    environment:
      - POSTGRES_HOST=db
      - POSTGRES_DB=nextcloud
      - POSTGRES_USER=nextcloud
      - POSTGRES_PASSWORD
      - REDIS_HOST=redis
      - NEXTCLOUD_ADMIN_USER
      - NEXTCLOUD_ADMIN_PASSWORD
      - NEXTCLOUD_TRUSTED_DOMAINS=${DOMAIN}
      - TRUSTED_PROXIES=172.28.0.0/24
      - OVERWRITEPROTOCOL=https
    volumes:
      - nextcloud_data:/var/www/html
    networks:
      - internal
    depends_on:
      - db
      - redis

  cron:
    image: nextcloud:35-apache
    restart: unless-stopped
    entrypoint: /cron.sh
    volumes:
      - nextcloud_data:/var/www/html
    networks:
      - internal
    depends_on:
      - nextcloud

  db:
    image: postgres:17-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_DB=nextcloud
      - POSTGRES_USER=nextcloud
      - POSTGRES_PASSWORD
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - internal

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    networks:
      - internal

networks:
  internal:
    ipam:
      config:
        - subnet: 172.28.0.0/24

volumes:
  caddy_data:
  nextcloud_data:
  db_data:

Что здесь не очевидно:

  • Наружу открыты только порты 80 и 443 у Caddy. База, Redis и сам Nextcloud порты не публикуют, они общаются по внутренней сети internal. Почему так, объяснено в статье про Docker.
  • Адрес сети задан вручную (172.28.0.0/24). TRUSTED_PROXIES перечисляет прокси, чьим заголовкам с настоящим адресом посетителя Nextcloud верит. Подсеть должна быть известна заранее, иначе Docker выдал бы любые адреса и перечислять было бы нечего. Если эта подсеть у вас уже занята другим проектом, Docker ответит invalid pool request: Pool overlaps with other one on this address space. Возьмите тогда другую, например 172.29.0.0/24, и поправьте её в TRUSTED_PROXIES и в разделе networks.
  • OVERWRITEPROTOCOL=https сообщает Nextcloud, что снаружи его открывают по HTTPS, хотя Caddy передаёт запросы ему по HTTP. Без этого ссылки и перенаправления получались бы с http://.
  • NEXTCLOUD_TRUSTED_DOMAINS — список адресов, по которым Nextcloud готов отвечать. Остальные он отклонит.
  • Образ nextcloud:35-apache закреплён на 35-й главной версии: docker compose pull будет приносить обновления внутри неё и не перепрыгнет на следующую без вашего ведома. Тег стоит в обоих сервисах, nextcloud и cron.
  • У контейнера cron другая точка входа: /cron.sh запускает фоновые задачи Nextcloud каждые пять минут. Том с данными у него общий с основным контейнером.
  • nextcloud в POSTGRES_USER и POSTGRES_DB — это имя администратора базы и имя самой базы. В PostgreSQL учётная запись называется ролью. Рабочей ролью Nextcloud станет другая, которую он создаст сам: об этом ниже, в разделе про восстановление.

Проверьте файл и запускайте. Первый запуск скачает около 2,7 ГБ образов, так что подождите:

docker compose config --quiet && echo OK
docker compose up -d

Первая установка

При первом запуске контейнер сам развернёт Nextcloud и создаст базу, обычно это занимает пару минут. Чтобы не гадать, спросите у Nextcloud, готов ли он:

docker compose exec -u www-data nextcloud php occ status
  - installed: true
  - version: 35.0.2.2
  - versionstring: 35.0.2
  - edition:
  - maintenance: false
  - needsDbUpgrade: false
  - productname: Nextcloud
  - extendedSupport: false

occ — консольный инструмент самого Nextcloud, через него делается почти всё администрирование. Запускать его нужно от пользователя www-data (за это отвечает -u www-data), иначе файлы получат неправильного владельца. Пока строки installed: true нет, подождите.

Откройте домен в браузере. Вас встретит форма входа: логин admin и пароль из .env.

Страница входа в Nextcloud

После входа Nextcloud покажет приветственные окна, закройте их. Затем откройте «Параметры» → «Администрирование» → «Общие сведения»: там собраны проверки безопасности и настроек.

Предупреждения после установки: что и как убрать

Те же проверки запускаются из консоли: docker compose exec -u www-data nextcloud php occ setupchecks. На свежей установке с нашими файлами они показали четыре замечания, и каждое лечится своей командой.

1. Фоновые задачи не запущены. Проверка писала (текст проверок Nextcloud выводит на английском):

✗ Cron last run: Last background job execution ran 56 years ago. Something seems wrong.

«56 лет назад» значит, что фоновая задача не выполнялась ни разу. Это было сразу после запуска: контейнер cron срабатывает раз в пять минут и мог просто не успеть. Но по умолчанию Nextcloud выполняет фоновые задачи при открытии страниц (режим AJAX), а для сервера правильнее режим cron, когда ими занимается отдельный контейнер. Переключим:

docker compose exec -u www-data nextcloud php occ background:cron
Set mode for background jobs to 'cron'

2. Нет окна обслуживания. Это время суток, в которое Nextcloud запускает тяжёлые ежедневные задачи. Без него они могут стартовать в самый разгар работы. Выберите своё значение: найдите ночные часы по местному времени и переведите их в UTC. Окно длится четыре часа от заданного:

docker compose exec -u www-data nextcloud php occ config:system:set maintenance_window_start --type=integer --value=1
System config value maintenance_window_start set to integer 1

У нас окно стало «с 1:00 до 5:00 UTC».

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

docker compose exec -u www-data nextcloud php occ maintenance:repair --include-expensive

4. Не отдаётся HSTS. Заголовок мы добавили в Caddyfile, а проверка всё равно ругалась. Похоже, Nextcloud проверяет себя по собственному адресу, в обход Caddy. Укажем ему настоящий адрес (подставьте свой домен):

docker compose exec -u www-data nextcloud php occ config:system:set overwrite.cli.url --value=https://cloud.example.com/

После этого проверка писала: «Your server is correctly configured to send security headers». Если вы правите Caddyfile уже после запуска, примените правку без перезапуска контейнера: docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile.

Необязательное замечание касается кода страны для телефонных номеров в профилях. Чтобы убрать его, укажите свою страну (замените DE на код своей):

docker compose exec -u www-data nextcloud php occ config:system:set default_phone_region --value=DE

Что получилось на странице администратора:

Проверка безопасности и параметров Nextcloud в панели администратора после настройки

Остались информационные пункты: служба развёртывания AppAPI (нужна только для внешних приложений), напоминания про двухфакторную аутентификацию, настройку почты и идентификатор сервера (нужен, если Nextcloud работает на нескольких серверах PHP). В консольной проверке есть ещё два пункта про определение адреса клиента, Brute-force Throttle и Forwarded for headers: из консоли у запроса нет адреса, поэтому они там срабатывают, а в веб-интерфейсе этих предупреждений нет. Почту и двухфакторную аутентификацию настройте сами: первое нужно для сброса пароля, второе для защиты входа.

Предупреждение «Ошибки в журнале» относится к двум записям от самых первых минут. Первый запуск фоновых задач в консоли завершился исключением базы (duplicate key value violates unique constraint "class_index"), а повторный запуск cron.php прошёл с кодом 0. Похоже, что задачи регистрировались параллельно с работой контейнера cron. Если у вас запись в журнале одна-две и больше не растёт, это можно не трогать.

Проверка работы: загрузка файла по WebDAV

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

echo "hello from webdav" > hello.txt
curl -u admin:ПАРОЛЬ -T hello.txt -w "http=%{http_code}\n" https://cloud.example.com/remote.php/dav/files/admin/hello.txt
http=201
curl -u admin:ПАРОЛЬ https://cloud.example.com/remote.php/dav/files/admin/hello.txt
hello from webdav

Код 201 означает, что файл создан. Проверим и перенаправление для календарей:

curl -sI https://cloud.example.com/.well-known/caldav
HTTP/2 301
location: /remote.php/dav

Теперь в приложениях Nextcloud для телефона и компьютера достаточно ввести адрес https://cloud.example.com, логин и пароль.

Лимит загрузки

Размер загружаемых файлов ограничивают переменные PHP_UPLOAD_LIMIT и PHP_MEMORY_LIMIT. По документации образа обе по умолчанию равны 512 МБ. Чтобы загружать файлы больше, добавьте в раздел environment сервиса nextcloud строку - PHP_UPLOAD_LIMIT=2G и пересоздайте контейнер командой docker compose up -d.

Резервная копия

Копировать нужно две вещи: базу данных и каталог /var/www/html внутри контейнера (на сервере это том nextcloud_data): в нём конфигурация, ваши файлы и установленные приложения. Чтобы копии были согласованными, на время включите режим обслуживания, в нём сайт недоступен:

mkdir -p ~/backups
docker compose exec -u www-data nextcloud php occ maintenance:mode --on
docker compose exec -T db pg_dump -U nextcloud nextcloud | gzip > ~/backups/nextcloud-db-$(date +%F).sql.gz
docker run --rm -v nextcloud_nextcloud_data:/data:ro -v ~/backups:/backup alpine \
  tar czf /backup/nextcloud-files-$(date +%F).tgz -C /data .
docker compose exec -u www-data nextcloud php occ maintenance:mode --off
chmod 600 ~/backups/nextcloud-*

Что делают команды. exec -T отключает псевдотерминал, иначе вывод pg_dump испортится. pg_dump -U nextcloud nextcloud выгружает базу в текст, gzip сжимает. Второй docker run пакует том с файлами, как в статье про n8n. Права 600 на архивы нужны, потому что в них лежит config.php с паролями. Если какая-то команда по пути упала, не забудьте выключить режим обслуживания вручную: последняя occ с --off.

У нас дамп чистой установки занял 319 КБ, а архив каталога — 348 МБ: в него попадает и код Nextcloud, поэтому на рабочем сервере архив будет весить больше ваших файлов. Имя тома nextcloud_nextcloud_data состоит из названия папки проекта и названия тома; своё посмотрите командой docker volume ls. Вместе с архивами сохраните файлы .env, compose.yaml и Caddyfile: без паролей из .env восстановление остановится на первом шаге.

Восстановление базы мы проверили на отдельном пустом PostgreSQL (делайте так и вы, на другой машине или в отдельном проекте, а не на рабочей базе). С первого раза получили 242 одинаковые ошибки:

ERROR:  role "oc_admin" does not exist

Nextcloud при установке создаёт для себя отдельную роль в базе. У нас она называется oc_admin, а таблицы принадлежат ей. Имя и пароль роли записаны в config.php как dbuser и dbpassword, их можно прочитать так:

docker compose exec -u www-data nextcloud php occ config:system:get dbuser
oc_admin
docker compose exec -u www-data nextcloud php occ config:system:get dbpassword

При восстановлении эта роль должна уже существовать. Сначала создайте её с тем же паролем, потом загружайте дамп (подставьте имя своего файла из ~/backups):

docker compose exec -T db psql -U nextcloud -d nextcloud -c "CREATE ROLE oc_admin LOGIN PASSWORD 'ПАРОЛЬ_ИЗ_CONFIG_PHP'"
gunzip -c nextcloud-db-2026-10-30.sql.gz | docker compose exec -T db psql -U nextcloud -d nextcloud -q

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

Обновление

Обновления внутри 35-й версии приходят так:

docker compose pull
docker compose up -d

Перед этим сделайте резервную копию. Nextcloud сам обновит базу и приложения при первом запуске нового контейнера. Между главными версиями переходите по одной, 35 → 36 → 37 и так далее: в документации образа сказано, что обновляться можно только на одну главную версию за раз. Для этого поменяйте тег в compose.yaml в обоих сервисах, nextcloud и cron. Версии пропускать нельзя, поэтому не откладывайте обновления на годы.

Что творится в журнале в первые минуты

Как только домен получил сертификат, он попадает в публичные журналы выпущенных сертификатов, а боты за ними следят. В журнале веб-сервера Nextcloud через считанные минуты после запуска мы увидели вот такие запросы:

"GET /.env HTTP/1.1" 404 ... "Mozilla/5.0 (l9scan/...; +https://leakix.net)"
"GET /.git/config HTTP/1.1" 404 ...
"GET /.env.local HTTP/1.1" 404 ...

Всего таких строк в журнале набралось одиннадцать. Это не сбой, так выглядит обычный интернет: сканеры ищут забытые файлы с паролями. В корне сайта таких файлов нет, и Nextcloud отвечает 404. На этом фоне помогают простые привычки: длинный пароль администратора, двухфакторная аутентификация, обновления по мере выхода и порты базы и Redis, закрытые от интернета (в нашей схеме они вообще не публикуются).

Адрес посетителя в журнале контейнера nextcloud не виден: там указан адрес Caddy (у нас 172.28.0.6), потому что запросы приходят через прокси. Настоящие адреса смотрите в журнале Caddy: docker compose logs caddy, в записях о запросах есть поле remote_ip.

Частые вопросы

Можно ли использовать MariaDB вместо PostgreSQL?

Можно: Nextcloud поддерживает обе базы, и в документации образа есть примеры с MariaDB. Мы взяли PostgreSQL и проверяли именно его.

Нужен ли Redis, если пользователь один?

Система заработает и без него, но Redis занимает 12 МБ и разгружает базу при синхронизации, поэтому мы его оставили.

Контейнер nextcloud постоянно перезапускается, что делать?

Посмотрите последние строки журнала: docker compose logs --tail 50 nextcloud. Чаще всего причина в базе, которая ещё не запустилась, или в ошибке в .env. Если контейнер перезапускается из-за нехватки памяти, поможет файл подкачки, но надёжнее взять сервер побольше.

Как добавить второй сервис на тот же сервер?

Добавьте в Caddyfile блок с другим поддоменом и подключите контейнер нового сервиса к сети internal, иначе Caddy его не найдёт. Тогда один Caddy обслуживает все сервисы, а порты 80 и 443 остаются у него одного.

Что дальше

Основы Docker — в статье «Docker и Docker Compose на VPS». Если хотите автоматизировать работу, например присылать уведомления по событию, загляните в статью про n8n. Сервер под эти задачи можно подобрать на странице тарифов VPS.

Похожее

Все статьи
Схема автоматизации на полу дата-центра: светящиеся узлы с иконками соединены линиями, тарелка запускает цепочку

n8n на VPS: установка через Docker Compose, HTTPS и автозапуск

n8n на своём VPS поднимается через Docker Compose за один вечер: два контейнера (сам n8n и обратный прокси Caddy, который сам выпускает HTTPS-сертификат), три небольших файла и одна команда запуска. Ниже — готовые файлы, объяснение каждой переменной, первый сценарий с…

Контейнеры из стекла, сложенные штабелем в зале дата-центра, тарелка поднимает один контейнер лучом

Docker и Docker Compose на VPS с Ubuntu: установка и первый запуск

Docker на VPS ставят из официального репозитория Docker, а не из пакета docker.io, который лежит в Ubuntu: версия там свежее, а Compose приходит готовым плагином. Установка — это несколько команд, которые можно копировать подряд, ниже они разобраны по шагам. А…