Привет, с вами снова Александр Константинов из Cloud.ru. Раньше я пользовался Notion, хранил там свои заметки, обучающие материалы, данные по своим стартапам. Но зарубежные SaaS-провайдеры ушли, и моя база превратилась в кирпич: она есть, но легально пользоваться ей невозможно. И это еще позитивный сценарий, потому что провайдер мог просто все безвозвратно удалить.

Сейчас, конечно, появляются другие сервисы, но все-таки у SaaS есть некоторые ограничения. И основное из них в том, что вектор их развития не подвластен пользователю. Плюс данные хранятся где-то там, кто-то ими управляет, но не я. А хочется все-таки делать это самостоятельно — это же моя база.

Я решил развернуть базу-знаний на wiki-движке Outline, потому что это полная замена Notion. У него хорошая функциональность, он простой в работе и с понятным интерфейсом. Что у меня получилось и как такое повторить, подробно рассказал в статье.

Поскольку я работаю в облачном провайдере, то было логично попробовать развернуть базу знаний на виртуальной машине и посчитать, сколько это будет стоить. Забегая вперед, скажу, что база знаний объемом в 10 000 заметок в облаке обошлась мне в 146 рублей в месяц. И так не потому, что я сотрудник компании, а потому что для этой цели можно использовать виртуальную машину и объектное хранилище с free tier. Если подключить к базе AI или масштабировать ее до уровня корпоративного решения, тогда да, придется запускать еще другие сервисы, а они уже дороже.

Что будем делать и что нам понадобится

Cоздадим виртуальную машину Ubuntu 22.04, настроим для нее публичный IP-адрес, создадим бакет в Object Storage и настроим CORS для него.

На виртуальной машине настроим Docker и Docker Compose, развернем сервис Outline, подключим его к Object Storage и GitLab и опубликуем на сервере nginx, выпустим SSL-сертификат в сервисе Let’s Encrypt.

В итоге получим надежную схему, где файлы хранятся в Object Storage, а клиентский трафик шифруется HTTPS.

Какие будем использовать сервисы:

  • Виртуальная машина с free tier — бесплатная ВМ заданной конфигурации. Настройки поменять не получится, но ее ресурсов хватит для базы знаний, в которой поместится до 10 000 заметок и которой будут пользоваться до 20 человек. Если нужно больше, придется выбрать другую виртуальную машину, но уже без free tier.

  • Публичный IP-адрес для доступа к базе знаний через интернет — 146 рублей в месяц.

  • Объектное хранилище с free tier — бесплатные 15 ГБ в стандартном S3-хранилище каждый месяц. Если потребуется больше места, то хранилище будет платное.

  • Docker — система контейнеризации.

  • Docker Compose — инструмент для запуска и управления Docker-контейнерами.

  • Outline — open-source система вики.

  • Бесплатный сервис nip.io для получения публичного доменного имени и сертификата. Я использую собственное зарегистрированное доменное имя и SSL-сертификат для организации доступа.

  • Nginx — веб-сервер для проксирования запросов и организации защищeнного HTTPS-доступа к базе знаний.

  • Let’s Encrypt — сервис для автоматического получения бесплатного SSL-сертификата.

  • GitLab — как провайдер для авторизации. Я выбрал его, потому что там хранятся мои проекты и мне удобно работать с ним. Список других доступных провайдеров можно найти в документе по аутентификации Outline.

  • Evolution Foundation Models — популярные open source модели в облаке, готовые к использованию. Сервис бесплатный до конца октября. Дальше будет тарификация по токенам в среднем 35 рублей за чтение и 70 рублей за запись. Цена за миллион токенов.

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

  • Keycloak для единой точки входа (SSO),

  • Kubernetes для деплоя сервисов и масштабирования,

  • Managed PostgreSQL и Managed Redis.

О разных вариантах развертывания корпоративных баз знаний с архитектурными схемами в облаке рассказываем на сайте, посмотрите, если интересно.

Шаги:

  1. Разворачиваем необходимые ресурсы в облаке.

  2. Настраиваем окружение на виртуальной машине.

  3. Настраиваем nginx и HTTPS.

  4. Настраиваем приложение в GitLab.

  5. Разворачиваем приложение.

  6. Настраиваем CORS в Object Storage.

  7. Удаляем доступ по SSH для виртуальной машины.

  8. Прикручиваем AI к базе знаний

Перед началом работы

  1. Регистрируемся в личном кабинете Cloud.ru или входим под своей учетной записью.

  2. Генерируем SSH-ключ по инструкции.

  3. Загружаем публичную часть SSH-ключа в облако Cloud.ru Evolution по инструкции.

1. Разворачиваем ресурсы в облаке

Создадим группу безопасности, виртуальную машину и бакет в Object Storage.

1. Создаем новую группу безопасности со следующими параметрами:

  • Указываем Название группы безопасности, например, outline-wiki.

  • Добавляем правила входящего и исходящего трафика.

Правила входящего трафика:

a. Протокол: TCP
b. Порт: 443
c. Тип источника: IP-адрес
d. Источник: 0.0.0.0/0

a. Протокол: TCP
b. Порт: 80
c. Тип источника: IP-адрес
d. Источник: 0.0.0.0/0

Правила исходящего трафика:

a. Протокол: Любой
b. Тип адресата: IP-адрес
c. Адресат: 0.0.0.0/0

2. Проверяем, что в личном кабинете на странице сервиса «Группы безопасности»:

  • отображается группа безопасности outline-wiki;

  • статус группы безопасности — «Создана».

3. Создаем бесплатную виртуальную машину со следующими параметрами:

  • В поле Название указываем название виртуальной машины, например, outline-wiki.

  • На вкладке Публичные выбираем образ Ubuntu 22.04.

  • В поле Логин указываем логин пользователя виртуальной машины, например, outline.

  • В разделе Метод аутентификации выбираем публичный ключ и пароль.

  • Указываем публичный ключ и пароль для создаваемого пользователя.

  • В поле Имя хоста указываем уникальное имя устройства, по которому можно идентифицировать виртуальную машину в сети, например, outline-wiki.

  • В поле Название загрузочного диска указываем outline-wiki-disk.

  • Включаем опцию Подключить публичный IP.

  • В группе Тип IP-адреса выбираем Прямой.

  • Выбираем группы безопасности SSH-access_ru.AZ-1, outline-wiki.

4. Проверяем, что в личном кабинете на странице сервиса «Виртуальные машины»:

  • отображается виртуальная машина outline-wiki;

  • статус виртуальной машины — «Запущена».

5. Создаем бакет в Object Storage со следующими параметрами:

  • В поле Доменное имя указываем outline-wiki (должно быть уникальным, замените на свое уникальное значение).

  • В поле Название указываем outline-wiki (совпадает с доменным именем).

  • В поле Глобальное название указываем outline-wiki (совпадает с доменным именем).

  • В поле Класс хранения по умолчанию выбираем стандартный.

  • В поле Максимальный размер указываем 10 ГБ.

6. Переходим в раздел Object Storage API. Сохраняем значения ID тенанта и Регион.

7. Проверяем, что в личном кабинете на странице сервиса Object Storage отображается бакет outline-wiki.

8. Создаем сервисный аккаунт администратора со следующими параметрами:

  • В поле Название указываем outline-object-storage-admin.

  • В поле Описание указываем «Аккаунт администратора Object Storage».

  • В поле Проект выбираем Пользователь сервисов.

  • Оставляем список Сервисы пустым.

  • В разделе Evolution Object Storage Роли выбираем s3e.admin.

9. Следуя аналогичной инструкции, создаем сервисный аккаунт пользователя со следующими параметрами:

  • В поле Название указываем outline-object-storage.

  • В поле Описание указываем «Аккаунт пользователя Object Storage».

  • В поле Проект выбираем Пользователь сервисов.

  • Оставляем список Сервисы пустым.

  • В поле Evolution Object Storage Роли выбираем s3e.viewer, s3e.editor.

10. Генерируем ключи доступа для обоих аккаунтов. Сохраняем Secret ID и Secret Key для обоих ключей.

2. Настраиваем окружение на виртуальной машине

Настраиваем систему и устанавливаем необходимые пакеты на виртуальной машине.

1. Подключаемся к виртуальной машине outline-wiki через серийную консоль или по SSH.

2. Обновляем систему и устанавливаем необходимые зависимости:

sudo apt update && sudo apt upgrade -y
sudo apt install unzip gnupg software-properties-common apt-transport-https ca-certificates python3-pip nginx snapd -y
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

3. Устанавливаем Docker и Docker Compose:

# Add Docker's GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
# Add Docker repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin docker-compose
# Add user to docker group
sudo usermod -aG docker $USER
newgrp docker

4. Проверяем, что Docker установлен корректно:

docker --version
docker compose version

3. Настраиваем nginx и HTTPS

Настраиваем службу nginx и обеспечиваем доступ по HTTPS.

1. Подключаемся к виртуальной машине outline-wiki через серийную консоль или по SSH.

2. Конфигурируем файрвол:

sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable

3. Создаем конфигурационный файл:

sudo nano /etc/nginx/sites-available/outline.conf

4. Вставляем конфигурацию, заменив <IP-адрес> на IP-адрес нашей виртуальной машины:

server {
   listen 80;
   server_name wiki.<IP-адрес>.nip.io www.wiki.<IP-адрес>.nip.io;

   location / {
       proxy_pass http://localhost:3000/;
       proxy_set_header Upgrade $http_upgrade;
       proxy_set_header Connection "Upgrade";
       proxy_set_header Host $host;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header X-Scheme $scheme;
       proxy_set_header X-Forwarded-Proto $scheme;
       proxy_redirect off;
   }
}

5. Применяем конфигурацию и перезапускаем nginx:

sudo ln -sf /etc/nginx/sites-available/outline.conf /etc/nginx/sites-enabled/outline.conf
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

6. Проверяем, что nginx работает:

sudo systemctl status nginx

Cервис nginx должен быть в статусе «active (running)».

7. Переходим по адресу http://wiki.<IP-адрес>.nip.io. Откроется страница с текстом 502 Bad Gateway.

8. Запускаем команду для выпуска SSL-сертификата:

sudo certbot --nginx -d wiki.<IP-адрес>.nip.io --redirect --agree-tos -m <EMAIL>

Где:

  • <IP‑адрес> — IP‑адрес вашей виртуальной машины.

  • <EMAIL> — ваш email.

9. После успешного выпуска сертификата, переходим по адресу https://wiki.<IP-адрес>.nip.io. Откроется страница с текстом 502 Bad Gateway. В свойствах сайта браузер отметит соединение как безопасное.

4. Настраиваем приложение в GitLab

Создаем приложение в GitLab-инстансе для интеграции с Outline.

1. Переходим в Настройки → Приложения в собственном или облачном GitLab-инстансе.

2. Создаем новое приложение со следующими настройками:

Имя: Outline
Redirect URI: https://wiki.<IP-адрес>.nip.io/auth/oidc.callback (меняем значение IP-адрес)
Scopes: выбираем openid, profile и email

3. Сохраняем приложение.

4. Сохраняем значения Application ID и Secret, они понадобятся в дальнейшем.

5. Разворачиваем приложение

Разворачиваем серверное приложение Outline с помощью Docker Compose.

1. Подключаемся к виртуальной машине outline-wiki через серийную консоль или по SSH.

2. Создаем структуру проекта:

mkdir -p $HOME/outline
cd $HOME/outline

3. Генерируем уникальные ключи и сохраняем их, они понадобятся в дальнейшем:

# Generate two random secrets for Outline
openssl rand -hex 32  # Save this as SECRET_KEY
openssl rand -hex 32  # Save this as UTILS_SECRET

# Generate database password
openssl rand -base64 15  # Save this as POSTGRES_PASSWORD

4. Создаем файл docker-compose.yml:

nano docker-compose.yml

5. Вставляем содержимое в файл docker-compose.yml, заменив переменные на значения:

Код
services:
  outline:
    image: flameshikari/outline-ru:0.86.0
    env_file: ./docker.env
    ports:
      - "3000:3000"
    volumes:
      - storage-data:/var/lib/outline/data
    depends_on:
      - postgres
      - redis
    environment:
      PGSSLMODE: disable

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    command: ["redis-server", "--bind", "0.0.0.0", "--port", "6379"]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 30s
      retries: 3

  postgres:
    image: postgres:15
    env_file: ./docker.env
    ports:
      - "5432:5432"
    volumes:
      - database-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-d", "outline", "-U", "user"]
      interval: 30s
      timeout: 20s
      retries: 3
    environment:
      POSTGRES_USER: 'user'
      POSTGRES_PASSWORD: <POSTGRES_PASSWORD>
      POSTGRES_DB: 'outline'

volumes:
  storage-data:
  database-data:

Где:

  • <POSTGRES_PASSWORD> — пароль от базы данных, сгенерированный ранее.

6. Создаем конфигурацию Redis:

nano redis.conf

7. Вставляем содержимое в файл:

bind 127.0.0.1
port 6379
timeout 0
save 900 1
save 300 10
save 60 10000
dbfilename dump.rdb
dir ./

8. Создаем файл docker.env:

nano docker.env

9. Вставляем содержимое в файл, заменив переменные на значения:

Код
NODE_ENV=production

# Application URL
URL=https://wiki.<IP-адрес>.nip.io
PORT=3000

# Secrets (use the generated values from Step 6)
SECRET_KEY=<SECRET_KEY>
UTILS_SECRET=<UTILS_SECRET>

# Database configuration
DATABASE_URL=postgres://user:<POSTGRES_PASSWORD>@postgres:5432/outline
PGSSLMODE=disable

# Redis configuration
REDIS_URL=redis://redis:6379

# File storage (using AWS S3)
FILE_STORAGE=s3
AWS_ENDPOINT_URL_S3=https://s3.cloud.ru
AWS_SDK_LOAD_CONFIG=1
AWS_USE_GLOBAL_ENDPOINT=false
AWS_S3_ADDRESSING_STYLE=path
AWS_ACCESS_KEY_ID=<TENANT_ID>:<SECRET_KEY_ID>
AWS_SECRET_ACCESS_KEY=<SECRET_KEY>
AWS_REGION=<REGION>
AWS_S3_CUSTOM_DOMAIN=<BUCKET_NAME>.s3.cloud.ru
AWS_S3_ENDPOINT=https://<BUCKET_NAME>.s3.cloud.ru
AWS_S3_UPLOAD_BUCKET_URL=https://<BUCKET_NAME>.s3.cloud.ru
AWS_S3_UPLOAD_BUCKET_NAME=<BUCKET_NAME>
AWS_S3_FORCE_PATH_STYLE=false
AWS_S3_ACL=private
FILE_STORAGE_UPLOAD_MAX_SIZE=26214400
AWS_S3_SIGNATURE_VERSION=v4

# GitLab OIDC Authentication
OIDC_CLIENT_ID=<GITLAB_APP_ID>
OIDC_CLIENT_SECRET=<GITLAB_CLIENT_SECRET>
OIDC_AUTH_URI=https://<GITLAB_DOMAIN>/oauth/authorize
OIDC_TOKEN_URI=https://<GITLAB_DOMAIN>/oauth/token
OIDC_USERINFO_URI=https://<GITLAB_DOMAIN>/oauth/userinfo
OIDC_USERNAME_CLAIM=username
OIDC_DISPLAY_NAME=GitLab
OIDC_SCOPES=openid email profile

# SSL Configuration
FORCE_HTTPS=true

# Rate limiting
RATE_LIMITER_ENABLED=true
RATE_LIMITER_REQUESTS=1000
RATE_LIMITER_DURATION_WINDOW=60

# Updates
ENABLE_UPDATES=true

# Logging
DEBUG=http
LOG_LEVEL=info

Где:

  • <SECRET_KEY>, <UTILS_SECRET> — секреты, сгенерированные на шаге 5.

  • <POSTGRES_PASSWORD> — пароль от базы данных, сгенерированный ранее.

  • <TENANT_ID> — ID тенанта сервиса Object Storage.

  • <REGION> — регион Object Storage.

  • <SECRET_KEY_ID>, <SECRET_KEY> — ID ключа и секретный ключ доступа к Object Storage. Используем ключи от аккаунта outline-object-storage.

  • <BUCKET_NAME> — название бакета Object Storage.

  • <GITLAB_APP_ID>, <GITLAB_CLIENT_SECRET> — ID и секретный ключ доступа к приложению GitLab.

  • <GITLAB_DOMAIN> — адрес сервиса GitLab. Может быть собственный или https://gitlab.com/.

10. Запускаем сервис:

docker compose up -d

11. Проверяем, что сервисы запущены:

docker compose ps

12. Переходим по адресу https://wiki.<IP-адрес>.nip.io. Откроется страница Outline, нас перенаправят в GitLab для авторизации.

13. Авторизуемся в GitLab, и нас автоматически перенаправят на страницу Outline.

6. Настраиваем CORS в Object Storage

Настраиваем CORS для бакета в Object Storage, чтобы разрешить безопасное взаимодействие с вашим приложением.

1. Подключаемся к виртуальной машине outline-wiki через серийную консоль или по SSH .

2. Устанавливаем зависимости командой:

pip install boto3

3. Создаем файл configure_cors.py и добавляем в него код:

nano configure_cors.py

4. Вставляем содержимое в файл конфигурации:

Код
import sys
import boto3
from botocore.client import Config

BUCKET = sys.argv[1]
ENDPOINT = sys.argv[2]
AK = sys.argv[3]
SK = sys.argv[4]
REGION = sys.argv[5]
FRONTEND_URL = sys.argv[6]

s3 = boto3.client(
    service_name='s3',
    aws_access_key_id=AK,
    aws_secret_access_key=SK,
    endpoint_url=ENDPOINT,
    region_name=REGION,
    verify=False,
    config=Config(s3={'addressing_style': 'virtual'})
)

cors_configuration = {
    'CORSRules': [{
        'AllowedMethods': ['PUT', 'POST'],
        'AllowedOrigins': [FRONTEND_URL],
        'ExposeHeaders': ['ETag'],
        'AllowedHeaders': ['*'],
        'MaxAgeSeconds': 60
    }]
}

s3.put_bucket_cors(Bucket=BUCKET, CORSConfiguration=cors_configuration)

5. Запускаем команду для обновления CORS-правил:

python3 configure_cors.py <BUCKET_NAME> https://s3.cloud.ru <TENANT_ID>:<SECRET_KEY_ID> <SECRET_KEY> <REGION> https://wiki.<IP-адрес>.nip.io

Где:

  • <BUCKET_NAME> — название бакета Object Storage.

  • <TENANT_ID> — ID тенанта сервиса Object Storage.

  • <REGION> — регион Object Storage.

  • <SECRET_KEY_ID>, <SECRET_KEY> — ID ключа и секретный ключ доступа к Object Storage. Используем ключи от аккаунта outline-object-storage-admin.

6. Переходим по адресу http://<IP-адрес>.nip.io. Откроется страница Outline.

7. Создаем новую заметку и загружаем в нее изображение.

7. Удаляем доступ по SHH для виртуальной машины

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

1. Переходим в раздел Сетевые параметры.

2. Нажимаем изменить группы безопасности для публичного IP-адреса.

3. Удаляем группу SSH-access_ru.

4. Нажимаем Сохранить.

5. Проверяем, что доступа нет — пробуем подключиться к виртуальной машине по SSH.

8. Прикручиваем AI к базе знаний

У меня есть open source проект на GitVerse, он готовый: берешь и запускаешь. Получится Telegram-бот, который будет работать с популярными готовыми open source моделями в облаке и базой знаний.

Городить бота стоит, если:

  • в Outline накопились регламенты, памятки, HOW-TO;

  • нужен поиск по заголовкам или контенту в заметках;

  • хочется спрашивать «человеческими» словами и сразу получать выдержку и ссылку на заметку.

Ключевая идея такого бота в том, что это «ленивый RAG без своей базы». То есть пользователь пишет вопрос, бот делает запрос к Search API Outline, берет топ-N результатов, склеивает их в промпт и отдает в LLM-endpoint. Полученный ответ бот возвращает в чат или REST без сохранения состояния.

Если говорить подробнее про сам процесс поиска и ответа, то выглядит он так:

  • извлечение ключевых слов для поиска. Вопрос прогоняется через промпт в LLM и достаются ключевые слова для поиска через API;

  • ключевые слова передаются в Outline или search?query= — получаются релевантные страницы;

  • подготовка контекста: найденные фрагменты форматируются в заголовок, summary, ссылку;

  • AI-обработка: контекст отправляется в сервис Evolution Foundation Models для генерации развернутого ответа;

  • формирование ответа: бот возвращает результат на языке пользователя.

Меня всегда волнует вопрос безопасности, у бота для базы знаний с этим все в порядке:

  • проверка токенов: любой запрос к Outline выполняется только после проверки валидности JWT/Api-Token;

  • токены ограничивают область видимости данных (RBAC из коробки). Пользователь может прочитать только те данные, к которым он имеет доступ в сервисе;

  • изоляция сессий: каждый чат = отдельный контекст, данные не смешиваются.

При всем этом инфраструктуры для такого бота нужно минимум: один контейнер FastAPI + адаптер Telegram. На развертывание уйдет 5 минут на той же виртуальной машине с free tier, где лежит сама база знаний, поэтому докупать железа не придется.

В итоге получится легкий, но полезный ассистент: ни собственной БД, ни cron-индексаций, только API Outline и два вызова LLM.

Вместо заключения

Работать с SaaS, конечно, удобно: не нужно ничего деплоить, можно залогиниться и сразу начать работать. Но надо понимать, что в таком случае ваши данные — это как бы не ваши данные, ими фактически управляет кто-то другой.

База знаний в облаке лучше SaaS-решения тем, что ей можно полностью владеть и управлять: в любой момент сделать локальный бэкап, не зависеть от SaaS, самостоятельно доработать решение под свои требования. Например, интегрировать AI-ассистента в интерфейс самой базы.

Кстати, 23 октября я проведу вебинар «Создаем корпоративную базу знаний Outline c SSO и AI в облаке». Пишите вопросы в комментариях и подключайтесь, я на все подробно отвечу.

Комментарии (2)


  1. rasperepodvipodvert
    02.10.2025 15:12

    Аа, вы просто прорекламировали cloud.ru - понятно...


  1. ksokol
    02.10.2025 15:12

    потому что это полная замена Notion

    Я сам пользуюсь с удовольствием Outline, очень его ценю, но называть его полной заменой Notion это как-то совсем уж.

    Notion это существенно больше, чем просто заметки.