BookStack на VPS: своя база знаний в Docker — установка и настройка

Документация компании обычно расползается по трём местам сразу: часть оседает в Google Docs, часть — в чатах, а часть живёт в голове у сотрудника, который вот-вот уйдёт в отпуск. Confluence решает проблему, но берёт деньги за каждого пользователя, а Notion хранит ваши данные на чужих серверах. BookStack — открытая база знаний на PHP (лицензия MIT) с жёсткой иерархией «полка → книга → глава → страница» и привычным WYSIWYG-редактором, который не пугает нетехнических сотрудников. Разворачивается на собственном сервере и не требует ни лицензий, ни ежемесячной подписки. Если своего сервера ещё нет, удобно начать с VPS под базу знаний.

Что понадобится

  • Чистый сервер Ubuntu 24.04 LTS с root-доступом.
  • Для команды до 15 человек достаточно 1 vCPU, 2 ГБ RAM и 20 ГБ диска; на 15-50 человек закладывают 2 vCPU, 4 ГБ RAM и 40 ГБ; при обилии вложений — 2-4 vCPU, 4-8 ГБ RAM, от 60 ГБ диска. BookStack заметно легче Nextcloud: файлов и превью почти нет, а основной вес дают картинки на страницах.
  • Домен с A-записью на IP сервера. Проверить связку можно так: dig +short wiki.example.com должен совпасть с адресом из curl -s ifconfig.me.
  • Открытые порты 80 и 443.
  • Стек из четырёх частей: nginx (приём HTTP/HTTPS и статика), php8.3-fpm (само приложение), mariadb-server (книги, страницы, роли и права), composer (PHP-зависимости) и git (получение кода). BookStack — обычное Laravel-приложение: Node.js на сервере не нужен, собранный фронтенд уже лежит в ветке release.

Установка пошагово

Шаг 1. Ставим пакеты из штатных репозиториев Ubuntu 24.04, без сторонних PPA:

apt update && apt -y full-upgrade
apt -y install nginx mariadb-server git unzip curl composer \
php8.3-fpm php8.3-mysql php8.3-xml php8.3-mbstring php8.3-curl \
php8.3-zip php8.3-gd php8.3-bcmath php8.3-intl

Проверяем набор расширений одной строкой — команда должна вернуть 10:

php -m | grep -icE 'mbstring|xml|curl|zip|gd|bcmath|intl|pdo_mysql|tokenizer|ctype|fileinfo'

Если не хватает pdo_mysql, значит забыли php8.3-mysql, и первая же миграция упадёт с ошибкой could not find driver. Фаервол сразу приводим в порядок:

ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp
ufw --force enable

MariaDB наружу не выставляем: ss -ltnp | grep 3306 должен показывать 127.0.0.1, а не 0.0.0.0.

Шаг 2. Создаём базу и отдельного пользователя под BookStack:

mariadb -u root -e "
CREATE DATABASE bookstack CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'bookstack'@'localhost' IDENTIFIED BY 'ДЛИННЫЙ_ПАРОЛЬ';
GRANT ALL PRIVILEGES ON bookstack.* TO 'bookstack'@'localhost';
FLUSH PRIVILEGES;"

Кодировку utf8mb4 ставят не для галочки: на обычном utf8 эмодзи и часть иероглифов в заголовках ломают вставку записи ошибкой Incorrect string value. Пользователя привязываем к localhost, а не к «%»: PHP и база живут на одной машине, ходить в базу по TCP снаружи незачем — и это ещё на пункт снижает поверхность атаки.

Шаг 3. Клонируем именно ветку release и ставим зависимости:

cd /var/www
git clone https://github.com/BookStackApp/BookStack.git --branch release --single-branch bookstack
cd bookstack
composer install --no-dev --optimize-autoloader

Частая ошибка на этом шаге — склонировать main: там лежат сырые непересобранные ассеты, и страницы открываются без стилей, голым HTML. Если так вышло, не переустанавливайте всё заново — достаточно git checkout release в уже склонированном каталоге.

Шаг 4. Настраиваем конфиг, генерируем ключ и накатываем миграции:

cp .env.example .env
nano .env

Минимум, что нужно поменять:

APP_URL=https://wiki.example.com
DB_HOST=localhost
DB_DATABASE=bookstack
DB_USERNAME=bookstack
DB_PASSWORD=ДЛИННЫЙ_ПАРОЛЬ

Затем ключ приложения и схема базы:

php artisan key:generate --no-interaction --force
php artisan migrate --no-interaction --force

Без первого шага приложение ответит No application encryption key has been specified — ключ шифрует сессии. Миграции создают все таблицы: книги, главы, страницы, роли и права доступа.

Шаг 5. Отдаём права на три каталога, в которые BookStack пишет сам — storage/, bootstrap/cache/ и public/uploads/:

chown -R www-data:www-data /var/www/bookstack
chmod -R 755 /var/www/bookstack/storage /var/www/bookstack/bootstrap/cache /var/www/bookstack/public/uploads

Забытый chown даёт при загрузке обложки книги ошибку «Permission denied»: в браузере просто бесконечно крутится спиннер, а причина видна только в storage/logs/laravel.log. Остаётся настроить блок nginx с TLS (передавая заголовки X-Forwarded-Proto и X-Forwarded-Host) и добавить в cron фоновые задачи Laravel — стандартный планировщик проекта.

Docker Compose (готовый конфиг)

Если не хочется ставить PHP, MySQL и nginx руками, есть контейнерная сборка. Готовим каталоги и файл переменных окружения:

mkdir -p /opt/bookstack/{mysql-data,bookstack-data}
cd /opt/bookstack
nano .env

Содержимое .env — пароли сгенерируйте свои, не оставляйте примеры:

DB_ROOT_PASSWORD=замените_на_свой_root_пароль
DB_PASSWORD=замените_на_свой_пароль_приложения
APP_URL=https://knowledge.your-domain.example

APP_KEY руками заполнять не нужно: контейнер сгенерирует его при первом запуске и допишет в переменные окружения. Сам docker-compose.yml:

services:
  mysql:
    image: mariadb:11
    container_name: bookstack-mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: bookstack
      MYSQL_USER: bookstack
      MYSQL_PASSWORD: ${DB_PASSWORD}
    volumes:
      - ./mysql-data:/var/lib/mysql
    networks:
      - bookstack-net
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 6
  bookstack:
    image: lscr.io/linuxserver/bookstack:latest
    container_name: bookstack
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy
    environment:
      PUID: "1000"
      PGID: "1000"
      TZ: "Europe/Moscow"
      APP_URL: ${APP_URL}
      DB_HOST: mysql
      DB_PORT: "3306"
      DB_DATABASE: bookstack
      DB_USERNAME: bookstack
      DB_PASSWORD: ${DB_PASSWORD}
    volumes:
      - ./bookstack-data:/config
    ports:
      - "127.0.0.1:6875:80"
    networks:
      - bookstack-net
networks:
  bookstack-net:
    driver: bridge

Образ lscr.io/linuxserver/bookstack упаковывает PHP-FPM, nginx и сам BookStack в один контейнер — файл проще, чем у «официального» варианта с двумя отдельными контейнерами. Поднимаем стек и следим за логами:

docker compose up -d
docker compose logs -f bookstack

Первый старт занимает 30-90 секунд: контейнер ждёт готовности MySQL по healthcheck, потом накатывает миграции. Порт смотрит только на 127.0.0.1, наружу отдаём через реверс-прокси с HTTPS. Данные для входа по умолчанию — admin@admin.com / password: сразу смените и почту, и пароль (Профиль → Edit Profile), а в Settings → Registration отключите публичную самостоятельную регистрацию.

Частые ошибки и решения

  • Белый экран или голая 500 после установки. Почти всегда виноваты права на storage/ и bootstrap/cache/ — выполните chown из шага 5. Если пусто, временно поставьте APP_DEBUG=true, посмотрите текст ошибки и обязательно верните APP_DEBUG=false: открытый debug на проде светит структуру путей и переменные окружения всем желающим. Второй частый источник — не выполненные миграции: php artisan migrate --force, затем php artisan cache:clear и php artisan config:clear.
  • Access denied для пользователя БД. Ошибка SQLSTATE[HY000] [1045] означает, что данные в .env не совпадают с реальными правами в базе. Проверьте блок DB_* и при необходимости пересоздайте пользователя (шаг 2). В контейнерной установке DB_HOST=localhost не сработает: укажите имя сервиса из docker-compose.yml (у нас mysql), а не localhost или 127.0.0.1 — сеть контейнера изолирована, и «localhost» внутри контейнера BookStack это он сам.
  • Интерфейс открывается «голым», стили и картинки не подгружаются. Виноват APP_URL: он должен точно совпадать с тем, как пользователи заходят на сайт, включая схему https и без слэша в конце. После правки обязательно php artisan config:clear и php artisan cache:clear, иначе Laravel продолжит использовать закешированное старое значение.
  • Не грузятся изображения и файлы. Поднимите лимиты PHP и перезапустите php-fpm:
upload_max_filesize = 20M
post_max_size = 25M
memory_limit = 256M
max_execution_time = 60

а если впереди nginx — снимите лимит на размер тела запроса и там:

client_max_body_size 25M;
  • Не приходят письма (приглашения, сброс пароля). Либо неверные данные SMTP, либо провайдер режет исходящий 25-й порт (это нормальная борьба со спамом). Используйте релей через 465 или 587:
MAIL_DRIVER=smtp
MAIL_HOST=smtp.yandex.ru
MAIL_PORT=465
MAIL_USERNAME=noreply@vash-domen.ru
MAIL_PASSWORD=пароль_или_apppassword
MAIL_ENCRYPTION=ssl
MAIL_FROM_NAME="BookStack"
  • Тормозит база на сотнях страниц. По порядку значимости помогают кеш конфига и роутов, OPcache, пересборка поискового индекса:
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan bookstack:regenerate-search

Учтите: при opcache.validate_timestamps=0 изменения в коде не подхватываются автоматически, и после обновления BookStack нужно вручную перезапускать PHP-FPM.

Бэкап и обновление

Данные BookStack живут в двух местах: в базе (структура книг, текст страниц, права) и в каталоге с файлами (загруженные изображения, обложки, аватары). Бэкапить нужно оба — дамп базы без файлов бесполезен, и наоборот.

docker exec bookstack-mysql sh -c 'exec mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" bookstack' > /opt/bookstack/backups/bookstack-$(date +%F).sql
tar czf /opt/bookstack/backups/bookstack-data-$(date +%F).tar.gz -C /opt/bookstack bookstack-data

Обновление контейнера — стандартный pull и пересоздание:

docker compose pull bookstack
docker compose up -d bookstack

Миграции схемы накатываются автоматически при старте новой версии. Перед мажорным обновлением (когда меняется первая цифра версии) сделайте свежий дамп и загляните в changelog: как и у большинства зрелых Laravel-приложений, обратные миграции BookStack штатно не поддерживает. Для обновления без Docker используйте git pull под техническим режимом:

php artisan down
git pull origin release
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan cache:clear
php artisan config:cache
php artisan up

php artisan down включает страницу техработ на время миграции — без него пользователь может открыть страницу ровно в тот момент, когда схема БД обновлена наполовину, и получить непредсказуемые ошибки.

Итог

BookStack — предсказуемая self-hosted база знаний: бесплатная по лицензии MIT, без платы за пользователей и с понятной иерархией полок, книг и страниц, которую освоит даже нетехнический сотрудник. Дальше стоит закрыть несколько дел: сменить пароль администратора и отключить публичную регистрацию, развести роли и права на уровне отдельных книг (например, «Финансы» — только для одного отдела), поднять HTTPS через реверс-прокси с автоматическим Let's Encrypt, при росте объёма вложений вынести файлы в S3-совместимое хранилище и подключить LDAP или SAML2, если в компании уже есть каталог пользователей. Под всё это нужен сервер с запасом по диску и стабильным аптаймом — подобрать подходящий VPS под базу знаний удобно в нашем каталоге.