Деплой на VPS — Versola Docs
VersolaVersola/docs
0.6.2versola.kzGitHub

Деплой на VPS

Разверните Versola на продакшен-VPS с помощью versola-cli, от начала до конца — реверс-прокси и TLS, секреты OpenBao, роль Postgres, миграции и проверка.

Эта страница описывает реальный продакшен-деплой с таргетом vps в versola-cli: Linux-сервер с нативным PostgreSQL, на котором CLI запускает сервисы Versola, их хранилище секретов (OpenBao) и реверс-прокси перед ними. Если вы просто хотите попробовать Versola, см. Установку — эта страница про то, как поднять (или передеплоить) настоящий инстанс.

Что CLI настраивает за вас

На vps versola-cli запускает всё, кроме базы данных:

  • central, auth, edge — Docker-контейнеры с network_mode: host, каждый привязан к 127.0.0.1.
  • OpenBao — хранилище, в котором лежат все секреты. CLI его запускает и при первом запуске сам инициализирует и настраивает.
  • Реверс-прокси — официальный образ nginx, вся конфигурация которого генерируется CLI. Он маршрутизирует ваш домен на auth/edge, отдаёт админ-консоль по /central/admin/ и — если у вас ещё нет своего веб-сервера — занимает порты 80/443 и сам получает сертификат Let’s Encrypt.

Что остаётся на вас:

  • PostgreSQL — нативная установка на сервере, не контейнер. CLI её не создаёт и ей не владеет; роль, базу и схемы вы создаёте один раз (SQL для роли CLI печатает сам).

Всё это работает как один Docker Compose-проект (versola-vps), поэтому versola status, down и uninstall охватывают весь деплой.

Требования

  • Linux VPS с Docker и плагином Docker Compose v2.
  • Нативный PostgreSQL 14+ и доступ суперпользователя к нему (sudo -u postgres psql), чтобы создать роль, базу и схемы Versola.
  • Домен для Versola (например, id.example.com) с DNS-записью A (и AAAA, если у сервера есть IPv6), указывающей на сервер.
  • Либо свободные порты 80 и 443 (их займёт прокси Versola и сам займётся TLS), либо ваш собственный веб-сервер на 80/443, который будет пробрасывать домен в Versola — см. Шаг 2.
  • versola-cli, установленный на самом VPS. Проверка памяти для vps читает /proc/meminfo на той машине, где запущена, а в режиме external прокси слушает только 127.0.0.1 сервера.
  • Достаточно свободной памяти: примерно 1 ГиБ, если Versola уже запущена (передеплой), и примерно 3 ГиБ для холодного старта — чуть больше, если OpenBao тоже ещё не поднят.

Шаг 0 — установка или обновление versola-cli

curl -fsSL https://raw.githubusercontent.com/versolauth/versola-cli/main/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"   # установщик не может изменить текущий shell

CLI ставится в ~/.local/bin. Если этой папки ещё нет в PATH, строка export делает versola доступной в текущем shell; добавьте её и в стартовый файл вашего shell (~/.bashrc, или ~/.zshrc для zsh), как подсказывает установщик, чтобы CLI находился и в новых терминалах.

Если CLI уже установлен, versola upgrade проверит последний релиз и заменит бинарник на месте (сборку из исходников, dev, он трогать откажется).

Шаг 1 — запустите doctor

versola doctor --target vps

Проверяет, что Docker доступен, плагин Compose v2 на месте и свободной памяти и места на диске хватает. configure/migrate/up делают те же проверки сами, так что шаг необязательный, но показывает полную картину заранее.

Шаг 2 — выберите, как Versola доступна снаружи

Флаг --proxy у configure/bootstrap решает, где слушает прокси Versola.

--proxy nginx (по умолчанию) — Versola занимает 80/443

Для сервера, на котором больше ничего не работает на веб-портах. Прокси слушает 80 и 443; при https:// в --auth-url он сам получает и продлевает сертификат Let’s Encrypt (ACME-модуль самого nginx, без certbot) и перенаправляет HTTP на HTTPS.

  • Для TLS --auth-url должен быть ровно https://<домен> — без порта и пути, не IP-адрес и не localhost. (Обычный http:// тоже работает, но без сертификата — только для тестов.)
  • DNS уже должен указывать на сервер, а порты 80/443 — быть доступны из интернета; если что-то уже занимает порт, configure сразу упадёт и назовёт его.
  • Сертификаты хранятся в Docker-томе versola-acme-vps, поэтому передеплой не запрашивает новый.
  • Для тестового деплоя добавьте --acme-staging: сертификат придёт из staging-среды Let’s Encrypt — браузеры ему не доверяют, зато нет лимитов продакшена.

--proxy external — за вашим веб-сервером

Для сервера, который уже обслуживает другие сайты на 80/443 со своим TLS. Тогда прокси Versola слушает только 127.0.0.1:2821 (обычный HTTP), а ваш веб-сервер пробрасывает на него домен и сам продолжает заниматься TLS. Он должен работать на том же хосте и передавать нужные заголовки — для nginx:

server {
    server_name id.example.com;
    listen 443 ssl;
    # ssl_certificate ... — ваша существующая настройка TLS

    client_max_body_size 8m;

    location / {
        proxy_pass http://127.0.0.1:2821;
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Connection "";
    }
}
  • Host — передаётся как есть. Versola ему не доверяет: issuer, адреса редиректов и htu для DPoP берутся из --auth-url, а не из заголовков запроса.
  • X-Forwarded-For — ему доверяют только от 127.0.0.1; Versola восстанавливает из него реальный адрес клиента и использует его для rate limiting.
  • X-Forwarded-Proto — схема, по которой пришёл браузер; стандартный заголовок для прокси, терминирующего TLS (сама Versola берёт схему из --auth-url).
  • client_max_body_size 8m — админ-консоль загружает темы и ключи; ваш сервер должен пропускать такой же размер тела, как прокси Versola.

Вся маршрутизация Versola (какой путь идёт в auth, какой в edge, админ-консоль) — внутри прокси Versola; ваш веб-сервер просто пробрасывает всё по домену.

Шаг 3 — configure

versola configure vps <version> \
  --auth-url https://id.example.com \
  --postgres-host 127.0.0.1:5432
# добавьте --proxy external для второго варианта из Шага 2

--auth-url должен в точности совпадать с origin, который видит браузер — схема и хост (порт только если он нестандартный, и никогда в режиме nginx). CLI отклоняет пути и query-строки (а хост приводит к нижнему регистру): несовпадение молча ломает редиректы и passkeys.

configure проверяет машину, тянет ghcr.io/versolauth/versola-tools:<version>, чтобы сгенерировать конфигурацию и админ-консоль этого релиза, пишет конфигурацию прокси и разрешает все секреты через OpenBao. Он не запускает ни одного сервиса Versola и не трогает базу — его можно запускать сколько угодно раз. Как и migrate и up, на vps он сначала просит подтверждение.

OpenBao настраивается автоматически. При первом запуске configure поднимает контейнер versola-openbao-vps и настраивает его: инициализация с одним ключом распечатки, распечатка, KV v2, аутентификация AppRole и политика, ограниченная секретами Versola. В ~/.versola/openbao/ он хранит два файла:

  • vps.json — учётные данные AppRole, с которыми CLI читает и пишет секреты. Они также печатаются при каждом запуске, включая secret ID, — учитывайте это, прежде чем куда-то вставлять вывод configure;
  • vps-admin.json — root token и ключ распечатки OpenBao. После каждого перезапуска OpenBao поднимается запечатанным; configure пересоздаёт контейнер, если он был остановлен, и распечатывает его этим ключом. Сохраните оба значения в надёжном месте вне сервера — восстановить их нельзя.

Каждый секрет — ключи подписи, ключи сессий, пароль Postgres, bootstrap-пароль админа — генерируется один раз, хранится в OpenBao и переиспользуется всеми последующими configure. Сами сервисы с OpenBao во время работы не общаются: configure записывает готовые значения рядом со сгенерированной конфигурацией, и OpenBao нужен только на время его запуска.

Хотите вести OpenBao сами? См. Управление OpenBao вручную.

Шаг 4 — создайте роль, базу и схемы Postgres

При первом configure пароль Postgres генерируется, поэтому роль можно создать только после этого запуска. configure печатает готовые команды:

Postgres: this was the first configure against this OpenBao, so a new password was
generated for Versola's Postgres role and stored there. Set it on the Postgres server
before `versola migrate` -- as the postgres superuser (e.g. sudo -u postgres psql):

  CREATE ROLE "versola_app" WITH LOGIN PASSWORD '…';   -- if the role doesn't exist yet
  ALTER ROLE "versola_app" WITH PASSWORD '…';          -- if it already exists

От имени суперпользователя Postgres выполните строку с CREATE ROLE (или ALTER ROLE, если вы создали роль заранее со своим паролем), затем базу и по одной схеме на сервис:

CREATE DATABASE auth OWNER versola_app;
\c auth
CREATE SCHEMA IF NOT EXISTS auth    AUTHORIZATION versola_app;
CREATE SCHEMA IF NOT EXISTS central AUTHORIZATION versola_app;
CREATE SCHEMA IF NOT EXISTS edge    AUTHORIZATION versola_app;

Выполняйте по одной команде — блок, в котором смешаны мета-команды psql (\c) и SQL, даёт непонятные ошибки разбора. Три сервиса используют одну базу, но у каждого своя схема; не пропускайте схемы — каждый сервис хранит историю миграций внутри своей схемы, и два сервиса в одной схеме падают с FlywayValidateException.

Пароль печатается только при этом первом запуске; дальше переиспользуется сохранённый.

Шаг 5 — бэкап базы (при передеплое)

Миграции нельзя откатить. При первой установке бэкапить ещё нечего; перед каждым следующим migrate снимайте дамп:

(umask 077; sudo -u postgres pg_dump -Fc auth > ~/versola-backup-$(date +%Y%m%d-%H%M%S).dump)

pg_dump работает от postgres, но пишет в stdout, поэтому файл создаёт ваш собственный shell — в вашей домашней папке, от вашего имени и с доступом только для вас (umask 077: в дампе хеши паролей и зашифрованные секреты). Храните копию где-то надёжно, а не только на сервере.

Шаг 6 — migrate

versola migrate

Применяет миграции каждого сервиса (auth, central и edge владеют каждый своей схемой) через один одноразовый контейнер, в котором есть миграции всех трёх сервисов и который завершается по окончании. Ни один сервер не запускается. Запуск записывается в ~/.versola, и status его показывает. Сами сервисы при старте миграции не применяют — они проверяют схему и отказываются стартовать на устаревшей.

Также доступно:

versola migrate --service auth         # мигрировать по одному сервису
versola migrate --dry-run              # проверить без применения

В релизах Versola до 0.6.2 включительно --dry-run сообщает о миграциях, которые просто ещё не применены, как об ошибке проверки («Detected resolved migration not applied to database») — это известная проблема dry-run, а не вашей базы; обычный versola migrate применяет их нормально.

Шаг 7 — up

versola up

Запускает стек и ждёт, пока он реально начнёт отвечать: сначала central (auth и edge синхронизируют с ним конфигурацию), затем auth и edge, последним — реверс-прокси; в конце проверяет, что запрос через прокси доходит до auth.

В режиме nginx с HTTPS, если сертификат не выпустился примерно за две минуты (DNS ещё не обновился, порт 80 недоступен), up выводит предупреждение, а не падает — сервисы подняты, а nginx продолжает попытки получить сертификат; смотрите docker logs versola-proxy.

При успехе печатает адрес, логин админа и файл с ADMIN_BOOTSTRAP_PASSWORD. Этот пароль временный, действует 24 часа: при первом входе с ним система попросит задать постоянный. Если он истёк до входа, перезапустите auth (docker restart versola-auth) — появится новый на 24 часа. В режиме external up также напомнит направить ваш веб-сервер на 127.0.0.1:2821.

Или всё сразу: bootstrap

versola bootstrap vps <version> --auth-url … --postgres-host … [--proxy external] выполняет Шаги 3, 6 и 7 за один раз. Если при первом запуске генерируется пароль Postgres, он останавливается перед миграциями (с ненулевым кодом выхода), чтобы вы выполнили SQL из Шага 4, — затем продолжите versola migrate и versola up.

Шаг 8 — проверка

versola status
Deployed version: 0.6.2 (target: vps)
Migrations applied: 2026-09-27 17:11:51

NAME                  IMAGE                                       …   STATUS
versola-auth          ghcr.io/versolauth/versola-auth:0.6.2       …   Up 2 minutes
versola-central       ghcr.io/versolauth/versola-central:0.6.2    …   Up 3 minutes
versola-edge          ghcr.io/versolauth/versola-edge:0.6.2       …   Up 2 minutes
versola-openbao-vps   openbao/openbao:2.5.4                       …   Up 10 minutes
versola-proxy         nginx:1.30-alpine                           …   Up 1 minute

(Таблица — это docker compose ps деплоя, здесь в сокращённом виде.)

Затем снаружи:

AUTH_URL=https://id.example.com   # ровно тот --auth-url, что передавали в configure
curl -s -o /dev/null -w "%{http_code}\n" "$AUTH_URL/.well-known/openid-configuration"   # 200

и войдите в админ-консоль по <auth-url>/central/admin/, прежде чем считать деплой завершённым.

Передеплой

Новая версия — те же три шага с новым номером версии и теми же флагами, что при первом деплое: configure их не запоминает. В частности, не забудьте --proxy external, если использовали его: без него прокси вернётся в режим nginx по умолчанию, и configure остановится на портах 80/443 вашего веб-сервера.

versola upgrade                     # держите CLI свежим
# бэкап базы (Шаг 5)
# уберите --proxy external, если первый деплой был в режиме nginx по умолчанию
versola configure vps <new-version> --auth-url https://id.example.com --postgres-host 127.0.0.1:5432 --proxy external
versola migrate
versola up

configure заново генерирует конфигурацию, прокси и админ-консоль из этого релиза; секреты берутся из OpenBao. configure и migrate не трогают работающие сервисы, поэтому старая версия продолжает обслуживать запросы, пока идут миграции, — перерыв только на время, пока up пересоздаёт контейнеры.

Откат

Если новый релиз только добавлял миграции и предыдущая версия нормально работает на новой схеме, откат — это просто versola configure vps <предыдущая-версия> … с вашими обычными флагами и затем versola up (при необходимости поставьте подходящий CLI со страницы релизов).

Если миграции нужно отменить, восстановите базу до запуска предыдущей версии — никогда не запускайте её на схеме, которую понимает только новый релиз:

versola down                                     # во время восстановления ничего не должно писать в базу
sudo -u postgres psql -c "DROP DATABASE auth;"
sudo -u postgres psql -c "CREATE DATABASE auth OWNER versola_app;"
sudo -u postgres pg_restore -d auth < ~/versola-backup-<timestamp>.dump   # файл открывает ваш shell, а не postgres
# уберите --proxy external, если первый деплой был в режиме nginx по умолчанию
versola configure vps <предыдущая-версия> --auth-url https://id.example.com --postgres-host 127.0.0.1:5432 --proxy external
versola migrate                                  # ничего не сделает: в дампе уже предыдущая схема
versola up

Всё, что было записано между дампом (Шаг 5) и откатом, теряется — поэтому дамп снимают прямо перед migrate.

Управление OpenBao вручную

Если у вас своя схема работы с OpenBao, передайте --setup-openbao в configure/bootstrap: тогда CLI никогда не обращается к admin API OpenBao, а настройку и сохранение учётных данных AppRole вы делаете сами. Распечатка после перезапуска тогда тоже на вас.

Порядок такой: один раз выполните versola configure vps … --setup-openbao — он запустит контейнер versola-openbao-vps и остановится с ошибкой «no OpenBao credentials stored», так и должно быть, — выполните шаги ниже, сохраните учётные данные через versola secrets login и снова запустите тот же configure. TLS на этом OpenBao выключен (он слушает только 127.0.0.1:8200), но CLI bao по умолчанию ходит по https, поэтому каждой команде нужен BAO_ADDR:

# 1. Инициализация — сохраните ОБА напечатанных значения; восстановить их нельзя.
docker exec -it -e BAO_ADDR=http://127.0.0.1:8200 versola-openbao-vps \
  bao operator init -key-shares=1 -key-threshold=1

# 2. Распечатка — снова после каждого перезапуска контейнера.
docker exec -it -e BAO_ADDR=http://127.0.0.1:8200 versola-openbao-vps \
  bao operator unseal <ключ распечатки>

# 3–4. KV v2 и AppRole.
docker exec -it -e BAO_ADDR=http://127.0.0.1:8200 -e BAO_TOKEN=<root token> versola-openbao-vps \
  bao secrets enable -path=secret kv-v2
docker exec -it -e BAO_ADDR=http://127.0.0.1:8200 -e BAO_TOKEN=<root token> versola-openbao-vps \
  bao auth enable approle

# 5. Политика, ограниченная секретами Versola.
docker exec -i -e BAO_ADDR=http://127.0.0.1:8200 -e BAO_TOKEN=<root token> versola-openbao-vps \
  bao policy write versola-vps - <<'EOF'
path "secret/data/versola/vps/*" {
  capabilities = ["create", "read", "update"]
}
EOF

# 6. Роль AppRole, привязанная к ней (долгоживущие учётные данные для автоматического инструмента).
docker exec -it -e BAO_ADDR=http://127.0.0.1:8200 -e BAO_TOKEN=<root token> versola-openbao-vps \
  bao write auth/approle/role/versola-vps \
    token_policies="versola-vps" token_ttl=1h token_max_ttl=4h \
    secret_id_ttl=0 token_num_uses=0

# 7. Учётные данные, которые нужны CLI.
docker exec -it -e BAO_ADDR=http://127.0.0.1:8200 -e BAO_TOKEN=<root token> versola-openbao-vps \
  bao read auth/approle/role/versola-vps/role-id
docker exec -it -e BAO_ADDR=http://127.0.0.1:8200 -e BAO_TOKEN=<root token> versola-openbao-vps \
  bao write -f auth/approle/role/versola-vps/secret-id

Затем сохраните их — secret ID запрашивается скрытым вводом, поэтому не попадает в историю shell:

versola secrets login vps http://127.0.0.1:8200 <role-id>

Пишете политику на Windows? Сохраните HCL-файл в кодировке ASCII, а не в UTF-8 по умолчанию — BOM ломает парсер OpenBao с ошибкой illegal char at 1:1. Создайте файл локально, скопируйте его через docker cp и выполните bao policy write versola-vps /путь/внутри/контейнера.hcl.

Изменение сохранённого секрета

Секреты лежат в secret/versola/vps/{auth,central,edge}. Используйте bao kv patch — kv put заменяет весь путь и сотрёт остальные ключи — с root token из ~/.versola/openbao/vps-admin.json:

docker exec -it -e BAO_ADDR=http://127.0.0.1:8200 -e BAO_TOKEN=<root token> versola-openbao-vps \
  bao kv patch -mount=secret versola/vps/auth SOME_KEY='<значение>'

Затем выполните versola configure vps <текущая-версия> с вашими обычными флагами и versola up. POSTGRES_PASSWORD общий для всех трёх сервисов и должен совпадать в auth, central и edge — если значения расходятся, configure откажется продолжать.

Решение проблем

configure падает, потому что порт 80 или 443 занят. Там уже что-то слушает (обычно веб-сервер). Либо остановите его, либо используйте --proxy external за ним.

OpenBao не поднимается после перезагрузки или запечатан. Работающие сервисы это не затрагивает — OpenBao им во время работы не нужен. Следующий configure пересоздаст и распечатает его. С --setup-openbao распечатайте его сами (bao operator unseal).

«container name already in use». Контейнер с одним из фиксированных имён Versola существует в другом Compose-проекте. Проверьте через docker ps и docker compose ls, остановите и удалите эти контейнеры по имени и повторите запуск.

502 через ваш веб-сервер (режим external). Проверьте, что прокси отвечает локально: curl -H 'Host: id.example.com' http://127.0.0.1:2821/.well-known/openid-configuration. Если отвечает — проблема в пробросе вашего веб-сервера; если нет — смотрите versola status и docker logs versola-proxy.

Админ-консоль отдаёт 404 сразу после up. Обновите страницу — первый запрос сразу после старта может попасть на ещё прогревающийся сервис.

image not found. Проверьте формат версии — у тегов нет v в начале: 0.6.2, а не v0.6.2. Опубликованные версии — на странице пакета versola-tools.