Обновление¶
Как обновить инстанс VPN Hub на новую версию образа ghcr.io/alexeyshalaev/vpn-hub — для
каждого способа установки. Данные (БД, бэкапы, каталог провайдеров) живут в томах и переживают
пересоздание контейнера; обновляется только образ приложения.
Сначала сделайте бэкап БД
Перед каждым обновлением снимите резервную копию: админка → Резервные копии → создать.
Новый образ на старте накатывает миграции схемы, и миграция может быть необратимой.
Advisory-lock защищает от гонки нескольких инстансов, но не от «плохой» миграции. Свежий
.vhb-бэкап — единственный способ вернуться, если что-то пойдёт не так.
.vhb-бэкапы логические и не зависят от версии PostgreSQL — восстановление переносится между
версиями БД.
Требования¶
- Работающий инстанс, развёрнутый одним из способов (Compose, docker run, Kubernetes).
- Доступ к тому же
.env/ Secret и тем же томам (иначе потеряете данные). - Свежий бэкап БД (см. danger выше).
- Сохранённый мастер-ключ — он не меняется при обновлении, но без него не расшифровать секреты и бэкапы.
Как проходит обновление¶
Новая версия — это новый образ. При старте контейнер сам выполняет alembic upgrade head и
доводит схему БД до нужной ревизии. Накат сериализован транзакционным advisory-lock: если
поднимается несколько инстансов, второй ждёт первого и видит уже накатанную схему — параллельный
старт безопасен.
Планировщик — не то же самое, что миграции
Миграции при нескольких репликах безопасны, а планировщик — нет (лидер-элекшена нет, он работает в каждой реплике). Держите приложение на 1 реплике, чтобы бэкапы, мониторинг и синхронизация не дублировались. См. Переменные окружения.
Стратегия тегов¶
От выбора тега зависит воспроизводимость обновлений.
| Тег | Пример | Плюсы | Минусы |
|---|---|---|---|
latest |
:latest |
просто, всегда «последнее» | невоспроизводимо — pull может принести что угодно |
major.minor |
:1.2 |
автопатчи внутри минора, без сюрпризов мажора | тянет патчи молча |
| Полный semver | :1.2.3 |
воспроизводимо, обновляетесь осознанно | нужно вручную поднимать тег |
| По digest | :1.2.3@sha256:… |
гарантия того же байта образа | самый ручной |
Для прода — пин версии
В продакшене пиньте полный 1.2.3 (или digest) и поднимайте тег осознанно, прочитав changelog.
latest удобен для «попробовать», но не даёт понять, что именно у вас сейчас крутится.
Узнать digest установленного образа: docker image inspect ghcr.io/alexeyshalaev/vpn-hub:1.2.3 --format '{{index .RepoDigests 0}}'.
Docker Compose¶
Переключите версию в .env, если поднимаете тег (для latest шаг пропускается):
Затем вытяните новый образ и пересоздайте изменившиеся контейнеры:
Уберите повисшие старые образы, чтобы не копить диск:
Если инстанс ставился скриптом, обновляйте им же — он тянет образы, пересоздаёт
контейнеры и чистит старые образы сам, не трогая .env и тома:
curl -fsSL https://raw.githubusercontent.com/AlexeyShalaev/vpn-hub/master/deploy/scripts/update.sh | bash
Сменить версию — флагом --tag (он запишет VPNHUB_TAG в .env):
curl -fsSL https://raw.githubusercontent.com/AlexeyShalaev/vpn-hub/master/deploy/scripts/update.sh | bash -s -- --tag 1.2.3
Другой каталог установки — флагом --dir PATH (по умолчанию $HOME/vpn-hub).
Проверьте, что оба контейнера снова здоровы:
NAME IMAGE STATUS
vpnhub-app-1 ghcr.io/alexeyshalaev/vpn-hub:1.2.3 Up 12s (healthy)
vpnhub-db-1 postgres:17 Up 30s (healthy)
Тома переживают пересоздание
docker compose up -d пересоздаёт контейнер приложения, но именованные тома (pgdata,
backups, data) остаются на месте — данные не теряются. Пересоздавать БД не нужно.
Caddy-оверлей: закрепите его в COMPOSE_FILE
Если HTTPS у вас через оверлей Caddy, набор файлов должен быть закреплён строкой
COMPOSE_FILE=compose.yaml:caddy.compose.yaml в .env (install.sh --domain делает это
сам) — тогда обновление видит весь стек. Без неё docker compose up -d пересоздаст
приложение без Caddy-настроек (слетит VPNHUB_TRUSTED_PROXY, Caddy станет
orphan-контейнером); update.sh в этом случае громко предупредит и подскажет строку.
Один docker run¶
Обновление вручную — тем же образом, что и Compose, но по шагам. Вытяните новый образ:
Остановите и удалите старый контейнер (тома, переданные через -v, при этом не удаляются):
Запустите заново с теми же томами и тем же окружением, что и в первый раз, — поменялся только тег образа:
docker run -d --name vpn-hub \
--env-file .env \
-p 127.0.0.1:8000:8000 \
-v vpnhub-backups:/var/lib/vpnhub/backups \
-v vpnhub-data:/var/lib/vpnhub/data \
ghcr.io/alexeyshalaev/vpn-hub:1.2.3
Не меняйте порт внутри контейнера
Приложение слушает PORT (по умолчанию 8000), а встроенный HEALTHCHECK образа жёстко ходит
на :8000/healthz. Меняйте порт снаружи (-p 9000:8000), внутри оставляйте 8000 — иначе
healthcheck образа сломается. Подробнее — Переменные окружения.
Убедитесь, что контейнер поднялся и здоров:
Kubernetes¶
Поднимите тег в overlay, чтобы он остался в манифестах (overlays/bundled-db/kustomization.yaml
или overlays/external-db/kustomization.yaml):
Примените и дождитесь завершения раскатки:
Дождитесь, пока новый под станет Running и готов:
Секрет/ConfigMap не рестартят поды сами
Если поменяли только Secret или ConfigMap (например, значение переменной), под этого не заметит — образ тот же. Заставьте его перечитать окружение:
Откат
Если новая версия ведёт себя плохо — откатитесь на предыдущую ревизию Deployment:
Откат Deployment вернёт старый образ, но не откатит уже применённую миграцию БД — для этого
нужен бэкап. Ещё одна причина снимать .vhb перед обновлением.
Обновление из панели (кнопка «Обновить сейчас»)¶
Раздел Система показывает текущую версию и, если вышла новее, кнопку «Обновить сейчас».
Само приложение в контейнере пересоздать себя не может, поэтому кнопка делегирует рестарт
внешнему механизму — драйвер выбирается по настройкам (приоритет command → webhook → k8s).
Пока ни один не настроен, кнопка честно предлагает ручной путь и не имитирует прогресс.
После нажатия панель перезапускается, страница ждёт возврата новой версии и сама перезагружается. Данные в PostgreSQL сохраняются, миграции накатываются на старте.
Подключите оверлей selfupdate.compose.yaml — он поднимает Watchtower в режиме HTTP-API
(обновляет по запросу, без периодического поллинга) и включает кнопку:
# в .env (закрепите оверлей, чтобы пережил обновления):
COMPOSE_FILE=compose.yaml:selfupdate.compose.yaml # +:caddy.compose.yaml, если нужен HTTPS
VPNHUB_UPDATE_TOKEN=$(openssl rand -hex 32)
docker compose up -d
Кнопка дёргает http://watchtower:8080/v1/update — Watchtower тянет новый образ и пересоздаёт
только контейнер приложения (по label). Тег должен допускать обновление (VPNHUB_TAG=latest
или минорный :0.4); при пине точной версии обновлять будет нечего.
База deploy/k8s/base уже содержит ServiceAccount vpnhub + Role с правом patch только
на собственный Deployment (rbac.yaml), а Deployment ссылается на этот SA. Кнопка патчит образ
через API кластера, rollout выполняет kubelet — отдельный апдейтер не нужен, kubectl в образе
не требуется.
Обновляйте через apply -k, а не только set image
RBAC и serviceAccountName появляются на кластере только при kubectl apply -k.
Если вы ставили/обновляли панель через kubectl set image (или ваши манифесты новее
приложения), под работает под SA default без прав — кнопка перед применением сделает
пре-чек (SelfSubjectAccessReview) и, увидев отсутствие прав, честно покажет «примените RBAC»
вместо ошибки 403. Дожать на уже стоящей инсталляции:
Выключить: VPNHUB_UPDATE_K8S=false. Патч образа разойдётся с images: newTag в overlay —
для GitOps после обновления синхронизируйте newTag (как и при kubectl set image).
VPNHUB_UPDATE_COMMAND — произвольная shell-команда применения (в ней {version} →
целевая версия). Годится для нестандартных схем: примонтированный docker.sock, запись
флаг-файла, которую подхватывает systemd на хосте, и т. п. Значение исполняется как shell —
задавайте только доверенное.
Автообновления по расписанию (опционально)¶
Автоматический подъём образа удобен для домашнего/некритичного инстанса, но добавляет риск: любой
pull может принести несовместимую версию без вашего участия.
Помечайте label'ом только приложение, не базу
Настройте автообновление так, чтобы оно следило только за контейнером приложения
(WATCHTOWER_LABEL_ENABLE=true + label на сервисе app). Если под автообновление попадёт
контейнер db, вы можете внезапно получить мажорный апгрейд PostgreSQL (17→18) — а он
требует dump/restore и сломает базу. Базу под автообновление не ставьте.
Пример для Compose с Watchtower (поддерживаемый форк;
оригинал containrrr/watchtower архивирован в конце 2025):
services:
app:
labels:
com.centurylinklabs.watchtower.enable: "true"
watchtower:
image: ghcr.io/nickfedor/watchtower:latest
restart: unless-stopped
environment:
WATCHTOWER_LABEL_ENABLE: "true" # следить ТОЛЬКО за помеченными сервисами (app)
WATCHTOWER_CLEANUP: "true" # удалять старые образы
volumes:
- /var/run/docker.sock:/var/run/docker.sock
Оговорки автообновлений
- Пиньте минорный тег (
:1.2), а не:latest— так Watchtower принесёт патчи, но не перепрыгнет мажор. - Watchtower требует доступ к
docker.sock— это фактически root на хосте. Риск оправдан не везде. - Для важного прода — ручной путь: осознанный пин версии и обновление после чтения changelog. И бэкап всё равно снимайте.
Мажорное обновление PostgreSQL (17 → 18)¶
При пине образа postgres:17 база сама не прыгнет на 18 — тег фиксирует мажор. Смена мажора
PostgreSQL требует отдельной процедуры dump/restore (каталог данных /var/lib/postgresql/data
несовместим между мажорами), это не делается простым pull.
Логические бэкапы переносятся между версиями
.vhb-бэкапы VPN Hub — логические (дамп строк), они не зависят от версии PostgreSQL.
Самый простой путь на новый мажор БД: снять .vhb, поднять чистую базу нужной версии,
восстановить бэкап из админки.
Дальше: Docker Compose · Kubernetes · Скрипты установки · Переменные окружения