# Версии и откат

> История выкладок, причины отказа, отмена идущего деплоя и возврат на одну из последних выкаченных версий.

Раздел: Деплой
Источник: https://nodbox.ru/docs/deploy/versions/

---
Каждый деплой заводит в приложении новую версию, а все версии собраны в историю на вкладке «Версии». Работающая версия отвечает, пока собирается новая, а откат возвращает прежнюю за секунды: с тем же образом, переменными и размером, с которыми она работала.

## Версия хранит код, переменные и размер

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

Версия не меняется. Правка переменных или размера работающую версию не трогает и доезжает со следующим деплоем. Пересборка после ошибки и смена размера тоже создают новую версию, а не повторяют старую.

У версии есть номер внутри приложения: v1, v2 и так дальше. Номер не переиспользуется, и версия, которая не доехала, свой номер тоже занимает.

## История показывает путь каждого деплоя

В панели история лежит на вкладке «Версии» страницы приложения, версии идут от новых к старым. В API её отдаёт `GET /api/v1/applications/{id}/revisions`, одну версию - `GET /api/v1/revisions/{id}`. Обоим запросам хватает права токена «читать».

Строка истории в панели состоит из таких частей:

- номер версии и статус
- доля трафика, только у версии, которая отвечает сейчас
- сообщение коммита и короткий хеш, а у архива и образа - откуда взята версия
- автор и время заказа
- время деплоя, у идущей версии оно считается вживую
- кнопка «Сборка», она открывает [Лог сборки](/docs/observability/build-log/) этой версии

В ответе API у версии такие поля:

| Поле | Что отдаёт |
| --- | --- |
| `number` | номер версии внутри приложения |
| `status` | `provisioning`, `active`, `superseded` или `error` |
| `phase` | шаг деплоя, на котором версия сейчас или на котором остановилась |
| `failure_reason` | причина отказа, у версии без отказа пусто |
| `origin` | откуда взят код: `git`, `zip` или `image` |
| `url` | адрес выкаченной версии, до выката пусто |
| `commit` | ветка или тег, хеш, сообщение и автор коммита |
| `traffic_percent` | доля трафика: 100 у версии, которая отвечает, 0 у остальных |
| `duration_seconds` | сколько шёл деплой, у идущего пусто |
| `operation` | операция деплоя: `done`, `status`, `failure_reason` и `message` |

## Статус версии складывается из двух полей

Статус называет итог деплоя, фаза - шаг, на котором версия сейчас или на котором остановилась. В панели они сливаются в одно слово:

| В панели | `status` и `phase` | Что значит |
| --- | --- | --- |
| В очереди | `provisioning`, `queued` | версия заказана, работа не началась |
| Код загружается | `provisioning`, `fetching` | платформа клонирует репозиторий или распаковывает архив |
| Код получен | `provisioning`, `fetched` | исходники получены, сборка впереди |
| Образ собирается | `provisioning`, `building` | идёт сборка |
| Образ публикуется | `provisioning`, `pushing` | собранный образ сохраняется на платформе |
| Образ собран | `provisioning`, `built` | образ готов, запуск впереди |
| Деплоится | `provisioning`, `deploying` | приложение запускается |
| Работает | `active`, `deployed` | версия запущена и отвечает на запросы |
| Отработала | `superseded` | версия работала, потом её заменила следующая или остановка |
| Ошибка | `error` | версия не доехала или деплой отменён |

«Работает» значит ровно одно: `status = active` и `phase = deployed`. Версия остаётся на `deployed`, и когда приложение уснуло: сон версию не отменяет.

Фаза замирает там, где деплой оборвался. Статус `error` вместе с фазой `building` читается как «упало на сборке». Состояние самого приложения платформа считает отдельно, его слова разобраны в разделе [Состояния приложения](/docs/observability/states/).

## Версию можно заказать вручную

Новая версия заказывается из панели или по API. У приложения из репозитория она заводится ещё и сама, по push в выбранную ветку: [Автодеплой по push](/docs/deploy/auto-deploy/).

В панели:

- «Задеплоить снова» на странице приложения, рядом с состоянием
- «Задеплоить» на вкладке «Версии»
- «Загрузить новый архив» или «Новый архив» у приложения из архива: каждой версии нужна своя загрузка

По API это `POST /api/v1/applications/{id}/revisions` с правом токена «деплоить». Тело зависит от источника кода:

| Источник | Тело запроса |
| --- | --- |
| Репозиторий | необязательное `ref`: ветка или тег, без него деплоится ветка приложения |
| Архив | обязательное `upload_id` новой загрузки |
| Образ | пустое |

Деплоится последний коммит ветки или тега, конкретный коммит выбрать нельзя. Заголовок `Idempotency-Key` обязателен: повтор запроса по таймауту тогда не заведёт вторую версию. Ответ - `202` с операцией. Опрашивай `GET /api/v1/operations/{id}`, пока `done` не станет `true`: у операции деплоя `action` равен `deploy_revision`.

Платформа откажет в заказе в таких случаях:

- версия дороже работающей, а денег на балансе не хватает на неделю разницы, у первой версии разница равна всей цене
- проект заблокирован за долг
- почта аккаунта не подтверждена
- подключение к git-аккаунту отозвано или удалено
- приложение останавливается или удаляется, ответ `409`

## Работающая версия отвечает, пока собирается новая

Пока новая версия собирается и запускается, прежняя продолжает отвечать на запросы, а приложение остаётся в состоянии «Работает». Весь трафик переезжает на новую версию, когда она запустилась. Прежняя при этом получает статус «Отработала»: работает всегда ровно одна версия.

Сорвавшийся деплой работающую версию не трогает. Сборка не прошла, приложение не запустилось, деплой отменён или не уложился в срок: прежняя версия отвечает как отвечала. Если работающей версии ещё не было, как при первом деплое, приложение остаётся в состоянии «Ошибка».

Сборка идёт до 16 минут, весь деплой - до 48. Что не уложилось, платформа останавливает с причиной `timed_out`.

Новый заказ отменяет предыдущий деплой, если тот ещё идёт. Это касается и деплоя по push: два быстрых push подряд оставят в работе последний, а у первого будет причина `canceled`. Повторно нажимать «Отменить деплой» перед новым заказом не нужно.

## Идущий деплой можно отменить

В панели пока идёт деплой, рядом с состоянием приложения стоит кнопка «Отменить деплой». По API это `POST /api/v1/revisions/{id}/cancel` с правом токена «деплоить».

Ответ - `202` с операцией самого деплоя, своей операции у отмены нет. Опрашивай её, пока `done` не станет `true`.

Отменённая версия получает такой вид:

- `status` равен `error`, а `phase` остаётся на шаге, где деплой оборвался
- причина `canceled` лежит на операции, у самой версии `failure_reason` пуст
- в панели строка стоит со статусом «Ошибка» и пояснением «Операция отменена по запросу»

Отдельного статуса «Отменён» у версии нет: отменённый деплой не работает так же, как упавший. Повторить отменённую версию нельзя, закажи новую. У приложения из архива для неё нужна и новая загрузка.

Отмена - просьба, а не гарантия. Деплой, который успел доехать, пока просьба была в пути, завершится своим итогом. Пока идёт сборка, отмена занимает до минуты. У версии без идущего деплоя отмена отвечает `409`: отменять нечего.

Работающая версия продолжает отвечать. За отменённую версию платить не нужно, а письма о провале деплоя она не вызывает: отменил ты сам.

## Причина отказа называет, что случилось

Когда версия не доехала, панель показывает сообщение «Версия vN не доехала», причину словами и кнопку, которая открывает лог сборки или лог приложения. По API причина лежит в `failure_reason` у версии и у операции, а текст - в `message` операции. Значение причины стабильно, текст платформа может поправить.

У провала после сборки `failure_reason` версии может быть пуст, а причина лежит только на операции. Поэтому читай обе.

### Код не дошёл до сборки

| Причина | Что случилось | Что делать |
| --- | --- | --- |
| `source_unavailable` | платформа не добралась до исходников: подключение отозвано, репозиторий или ветка не найдены, загруженный архив пропал | проверь подключение и ветку в настройках приложения, а у архива загрузи его заново |
| `archive_invalid` | архив не разбирается | собери его заново обычным «сжать в zip» |
| `archive_encrypted` | архив защищён паролем | упакуй без пароля |
| `compression_unsupported` | архив сжат методом, который платформа не разбирает | упакуй обычным zip |
| `limit_exceeded` | исходников слишком много: объём, число файлов или время получения кода вышли за потолок | убери из проекта лишнее |

Что именно проверяет платформа в архиве и какие у неё потолки, разобрано в разделе [Источники кода](/docs/deploy/sources/).

### Сборка не прошла

| Причина | Что случилось | Что делать |
| --- | --- | --- |
| `language_not_detected` | платформа не нашла в проекте знакомых файлов сборки | положи файл зависимостей в корень, задай корневую папку или добавь Dockerfile: [Настройки сборки и свой Dockerfile](/docs/deploy/build-settings/) |
| `dockerfile_missing` | выбран сборщик `dockerfile`, а Dockerfile в корне сборки нет | положи файл или верни автоматический выбор сборщика |
| `root_directory_missing` | корневой папки сборки нет в исходниках | поправь `root_directory` в настройках приложения |
| `build_failed` | сборка дошла до работы и не собралась | прочитай [Лог сборки](/docs/observability/build-log/), проверь, что проект собирается у тебя локально, и закажи версию с исправлением |
| `build_timeout` | сборка не уложилась в 16 минут и остановлена | сократи сборку или убери из неё лишние шаги |
| `image_too_large` | собранный образ больше допустимого размера | убери из образа лишнее |

### Приложение не запустилось

Эти причины приходят после того, как образ собран и отправлен на запуск. Причину тут ищи в логе приложения, а не в логе сборки.

| Причина | Что случилось | Что делать |
| --- | --- | --- |
| `container_exited` | процесс приложения раз за разом завершается с ошибкой, в сообщении стоит код выхода | прочитай [Логи приложения](/docs/observability/logs/): там последние слова процесса |
| `out_of_memory` | приложению не хватило памяти, и процесс остановлен | возьми размер побольше: [Размеры приложения](/docs/apps/sizes/) |
| `port_unreachable` | процесс запустился и не отвечает на порту, в сообщении стоит номер порта | проверь, на каком порту слушает приложение, и сверь с настройкой: [Порт, протокол и проверка готовности](/docs/apps/runtime/) |

Падающий процесс платформа распознаёт на третьем перезапуске, примерно через 70 секунд после запуска, не дожидаясь пяти минут. Молчащий порт отличим от медленного старта только по сроку, поэтому такой приговор приходит позже, около пяти минут. Приложение, которое падает раз-другой и потом поднимается, до конца деплоя доезжает.

### Сбои деплоя без своей причины на версии

Эти причины лежат только на операции деплоя.

| Причина | Что случилось | Что делать |
| --- | --- | --- |
| `revision_not_ready` | приложение не запустилось, а точнее платформа не определила: например, образ не скачался. Упавший процесс и молчащий порт получают свои причины выше | загляни в логи и закажи версию с исправлением |
| `api_server_rejected` | платформа отклонила запрос, и повтор не поможет | напиши в поддержку |
| `timed_out` | деплой не уложился в 48 минут и остановлен | закажи версию ещё раз |
| `canceled` | деплой отменили | закажи новую версию |

### Сбой на стороне платформы

Две причины говорят, что дело не в твоём коде.

| Причина | Что случилось | Что делать |
| --- | --- | --- |
| `push_failed` | образ собрался, но не сохранился | закажи версию ещё раз |
| `internal` | сбой на нашей стороне, твой код тут ни при чём | закажи версию ещё раз, а если сбой повторяется, напиши в поддержку |

О провале деплоя платформа пишет письмом, и проект сам решает, приходит ли оно. Подробности - в разделе [Уведомления](/docs/project/notifications/).

## Откат возвращает то, что работало

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

В панели:

1. Открой вкладку «Версии».
2. Нажми «Откатить» в строке нужной версии.
3. Подтверди в окне «Откатиться на vN?».

Кнопка стоит не у каждой строки. Её нет у версии, которая уже отвечает, и у версии, которая ни разу не доезжала до запуска. Пока приложение не работает, кнопки нет совсем, а пока идёт другая работа, она недоступна. Агент откатывает фразой «верни предыдущую версию»: [Фразы для агента](/docs/automation/prompts/).

По API это `POST /api/v1/applications/{id}/rollback` с правом токена «деплоить» и телом `{"revision_id": "rev_..."}`. Ответ - `202` с операцией приложения, не версии: у неё `action` равен `rollback_application`. Опрашивай её, пока `done` не станет `true`. Ключ `Idempotency-Key` не нужен.

После отката:

- выбранная версия снова получает `status = active` и 100% трафика
- прежняя работавшая версия получает `superseded` и 0% трафика
- число версий в истории не меняется
- откат попадает в [Ленту событий](/docs/observability/events/)

Следующий деплой снимает закрепление: он создаёт новую версию, и трафик уходит на неё. Новая версия берёт код ветки и сегодняшние настройки приложения, а не версию, на которую ты откатился. Если сломанный коммит остался в ветке, он поедет снова. Исправь код или верни коммит, прежде чем деплоить или пушить.

### Когда откат отказывает

Запрос на откат отвечает `409` в трёх случаях, которые различает текст:

| Ответ | Что случилось |
| --- | --- |
| «Приложение не работает: откатывать нечего. Сначала запусти его.» | приложение остановлено или первый деплой не удался |
| «Эта версия ни разу не работала - откатиться на неё нельзя.» | версия не доезжала до запуска |
| «Эта версия уже обслуживает приложение.» | откатываться некуда: нужная версия уже работает |

Четвёртый случай - идущий деплой: откат отвечает `409` и просит дождаться деплоя или отменить его. Версия чужого приложения даёт `422`.

Откат на версию с размером крупнее работающей проверяет квоту проекта на процессор и память и баланс: нужны деньги на неделю разницы в цене. Квота отвечает `403`, деньги - `402`. Откат на размер поменьше этих проверок не встречает. Проект, заблокированный за долг, откатиться может, хотя новую версию не закажет.

Если версии уже нет на платформе, запрос принимается, а операция заканчивается ошибкой `revision_gone`: «Версии, на которую идёт откат, на платформе больше нет». Работающая версия при этом остаётся как была. Выбери версию поновее или задеплой заново.

## Платформа хранит три последние выкаченные версии

Платформа держит три последние версии, которые доезжали до запуска. Работающая входит в эти три, поэтому в обычном случае откатиться можно на две предыдущие. Версии, которые не доехали, в счёт не идут.

Всё, что старше этих трёх, платформа убирает раз в час. С версии уходят исходники, собранный образ и запись в истории. Пока в приложении меньше трёх выкаченных версий, убирать нечего. Запись о вышедшей из окна версии может пробыть в истории до следующего прохода уборки, и откат на неё уже отвечает `revision_gone`.

Уборка не трогает работающую версию, даже если она старше окна. Так бывает после отката: работает версия v3, а свежие v4 и v5 лежат рядом. Образ платформа удаляет, только если на него не ссылается ни одна оставшаяся версия.

Остановка приложения уносит с собой и прежние версии: после запуска откатиться на версии, выкаченные до остановки, нельзя, откат ответит `revision_gone`.

## Запуск после остановки берёт образ прежней версии

Запуск остановленного приложения заводит новую версию. Сборки нет: версия берёт образ последней версии, которая доезжала до запуска. Настройки она берёт сегодняшние, из приложения, а не из той версии. В этом запуск отличается от отката, который возвращает прежние настройки. Подробности - в разделе [Остановка и удаление](/docs/apps/lifecycle/).

## Деньги идут за работающую версию

Платить за приложение начинают с подтверждённого запуска. Версия, которая собирается, упала или отменена, ничего не стоит.

Цена берётся с размера работающей версии. После отката платишь по размеру той версии, на которую вернулся. Как списываются деньги, разобрано в разделе [Тарификация](/docs/project/billing/).

## Что дальше

- [Источники кода](/docs/deploy/sources/) - репозиторий, архив и образ
- [Лог сборки](/docs/observability/build-log/) - где видно, на какой строке сломалась сборка
- [Состояния приложения](/docs/observability/states/) - что значит «Работает», «Спит» и «Упало»
- [Лента событий](/docs/observability/events/) - деплои, откаты и падения в одном списке
- [Остановка и удаление](/docs/apps/lifecycle/) - как остановить приложение и запустить его снова
- [Типичные сбои и что делать](/docs/help/troubleshooting/) - куда смотреть, если деплой не прошёл
