Олег Марков
Хотфиксы - полное руководство по экстренным исправлениям в продуктиве
Введение
Хотфиксы (hotfix) — это экстренные исправления в рабочей (продуктивной) системе, которые нужно применить как можно быстрее, не дожидаясь планового релиза. Обычно это критичные баги в проде, уязвимости безопасности или ошибки, влияющие на деньги бизнеса.
Смотрите, здесь важно разделять две вещи:
- обычный релиз — планируемый, проходит полный цикл тестирования и согласований;
- хотфикс — вынужденная мера, когда ошибка уже в продакшене, а ждать следующего релиза нельзя.
В этой статье вы увидите, как:
- организовать процесс работы с хотфиксами;
- настроить ветвление в Git под hotfix-поток;
- минимизировать риск «залечить одно — сломать другое»;
- автоматизировать выкаты хотфиксов через CI/CD;
- документировать и откатывать хотфиксы.
Давайте разбираться по шагам, с живыми примерами и кодом там, где это помогает понять процесс.
Что такое хотфикс и когда он нужен
Основные признаки хотфикса
Хотфикс нужен, если выполняется хотя бы одно из условий:
- ошибка в продакшене уже видна пользователям;
- ошибка ведет к финансовым потерям (дублирование платежей, неверные суммы, недоступность оплаты);
- ошибка связана с безопасностью (утечки данных, обход авторизации, возможность эскалации прав);
- ошибка нарушает ключевую бизнес-функцию (невозможно оформить заказ, выписать счет, отправить отчет).
Для таких проблем вы не можете ждать:
- планового релиза раз в неделю или месяц;
- полного цикла регрессионного тестирования;
- завершения всех задач спринта.
Хотфикс — это компромисс между скоростью и безопасностью. Вы сознательно ускоряете путь исправления до продакшена, но при этом должны жестко контролировать риски.
Чем хотфикс отличается от обычного релиза
Коротко:
- цель хотфикса — исправить одну (максимум несколько) конкретных критичных проблем;
- объем изменений — минимально возможный, лучше всего точечный патч;
- время жизни ветки — короткое, от создания до релиза;
- тестирование — фокусное, вокруг исправляемой области, плюс базовые проверки.
Покажу вам типичный сценарий:
- В мониторинге видите всплеск ошибок 500 на эндпоинте
/checkout. - Логи показывают панику при обработке пустого списка товаров.
- Команда находит в коде место, где не проверяется длина списка.
- Создается ветка хотфикса из последнего релизного тега.
- Вносится минимальное исправление + тест.
- Гоняются быстрые тесты и, по возможности, часть регресса.
- Хотфикс выкатывается на прод.
- Изменения мёржатся обратно в основную ветку разработки.
Теперь давайте посмотрим, как это организовать в Git.
Ветвление и хотфиксы в Git
Базовая схема ветвления с поддержкой hotfix
Один из самых распространенных подходов — схема, похожая на Git Flow (или упрощенный вариант):
- main (или master) — стабильная ветка продакшена;
- develop — основная ветка разработки (опционально, если у вас есть отдельный поток фич);
- release/x.y.z — подготовка планового релиза;
- hotfix/x.y.z+1 — ветка экстренного исправления.
Смотрите, я покажу вам упрощенную модель:
- у вас есть релиз
v1.4.0(тег на ветке main); - в разработке уже идет
v1.5.0в ветке develop; - в продакшене находится код
v1.4.0; - неожиданно находится критичный баг.
В этом случае ветку хотфикса лучше всего создавать от тега релиза:
# Переходим в стабильную ветку
git checkout main
# Убеждаемся, что у нас последний код
git pull origin main
# Создаем ветку хотфикса от последнего релиза
git checkout -b hotfix/1.4.1 v1.4.0
# ^ имя ветки включает номер версии патча
Комментарии к процессу:
- мы не берем код из develop, чтобы не притянуть «сырые» фичи;
- стартуем ровно от того кода, который сейчас в продакшене;
- номер версии хотфикса — обычно патч-версия (x.y.Z).
Типичные правила именования веток hotfix
Хорошая практика — использовать шаблон:
hotfix/<версия>— например,hotfix/1.4.1;- или
hotfix/<ticket-id>-краткое-описание— например,hotfix/PROD-123-null-pointer.
Пример:
git checkout -b hotfix/PROD-123-fix-null-check v1.4.0
# PROD-123 - ID задачи в трекере (Jira, YouTrack и т.п.)
# fix-null-check - краткое описание сути хотфикса
Такой формат помогает:
- быстро понять, на какой релиз опирается ветка;
- найти задачу и описание проблемы в трекере;
- отследить историю хотфиксов.
Жизненный цикл ветки hotfix
Давайте посмотрим весь путь ветки хотфикса:
# 1. Создание ветки хотфикса
git checkout -b hotfix/1.4.1 v1.4.0
# 2. Вносим изменения
# Редактируем нужные файлы, добавляем тесты
# 3. Фиксируем изменения
git add .
git commit -m "Fix panic on empty cart in checkout"
# 4. Отправляем ветку на сервер
git push origin hotfix/1.4.1
# 5. Создаем Merge Request (Pull Request) в main
# (делается через GitLab/GitHub/Bitbucket UI)
# 6. После ревью мёржим в main (через UI)
# 7. Ставим тег новой версии на main
git checkout main
git pull origin main
git tag -a v1.4.1 -m "Hotfix 1.4.1 - fix panic on empty cart"
git push origin v1.4.1
После этого важный шаг — подтянуть хотфикс в другие ветки разработки.
Слияние хотфикса обратно в develop
Если у вас есть ветка develop, где уже живут новые фичи, ее нужно синхронизировать с тем, что вы исправили в main. Иначе в следующем релизе баг может вернуться.
Схема простая:
# Переходим в develop
git checkout develop
git pull origin develop
# Мержим изменения из main (в котором уже есть хотфикс)
git merge main
# Разрешаем конфликты, если они есть, затем:
git commit -m "Merge hotfix 1.4.1 from main into develop"
git push origin develop
Обратите внимание:
- в историю develop попадет тот же код исправления, что ушел в продакшен;
- если вы пользуетесь Git Flow, это стандартная практика после каждого релиза и хотфикса;
- от этого зависит целостность истории и отсутствие «возврата» старых багов.
Минимизация объема изменений в хотфиксе
Почему «чем меньше изменений — тем лучше»
При хотфиксе вы обычно действуете в условиях:
- ограниченного времени;
- стресса и давления от бизнеса;
- неполного понимания всех последствий.
Поэтому ключевая стратегия:
- менять только то, что действительно нужно для исправления;
- не «улучшать заодно» код вокруг;
- не рефакторить большие участки.
Это снижает вероятность:
- появления новых багов;
- сложных конфликтов при мёрже;
- непредсказуемых побочных эффектов.
Пример минимального патча
Представим, что у вас есть код расчета скидки, который ломается, если скидка не определена:
// calculateDiscount.go
package pricing
func CalculateFinalPrice(basePrice float64, discount *float64) float64 {
// Плохо - отсутствие проверки на nil приводит к панике
finalPrice := basePrice - *discount
if finalPrice < 0 {
return 0
}
return finalPrice
}
В продакшене вы видите панику при обработке nil скидки. Для хотфикса разумно сделать минимальное изменение:
// calculateDiscount.go
package pricing
func CalculateFinalPrice(basePrice float64, discount *float64) float64 {
// Хорошо - добавляем минимальную проверку указателя,
// не трогая остальной код функции
if discount == nil {
// Нет скидки - возвращаем базовую цену
return basePrice
}
finalPrice := basePrice - *discount
if finalPrice < 0 {
return 0
}
return finalPrice
}
И добавляем тест, чтобы зафиксировать поведение:
// calculateDiscount_test.go
package pricing
import "testing"
func TestCalculateFinalPrice_NoDiscount(t *testing.T) {
basePrice := 100.0
// Передаем nil - раньше тут была паника
final := CalculateFinalPrice(basePrice, nil)
if final != basePrice {
// Проверяем, что без скидки цена не меняется
t.Fatalf("expected %v, got %v", basePrice, final)
}
}
Как видите, мы:
- не переписывали алгоритм;
- не меняли сигнатуру функции;
- просто добавили безопасную проверку.
В хотфиксе такие маленькие, но точные изменения — именно то, что нужно.
Тестирование хотфиксов
Приоритеты в тестировании hotfix
Когда время ограничено, а вы выкатываете критичный хотфикс, полезно придерживаться приоритетов:
- Автоматические юнит-тесты вокруг исправленного кода.
- Интеграционные тесты на затрагиваемый сценарий.
- Smoke-тесты основного функционала после выката.
Главная идея — проверить:
- что баг действительно исправлен;
- что основная цепочка сценариев вокруг него не сломана.
Пример юнит-теста для хотфикса в API
Допустим, у вас в HTTP-хендлере была ошибка при обработке пустого тела запроса:
// handler.go
package api
import (
"encoding/json"
"net/http"
)
type OrderRequest struct {
Items []string `json:"items"`
}
func CreateOrderHandler(w http.ResponseWriter, r *http.Request) {
var req OrderRequest
// Баг - если тело пустое, Decode вернет EOF и мы ответим 500
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
// Логируем и отдаем 500
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
// Далее идут бизнес-операции...
}
Хотфикс может выглядеть так:
// handler.go
package api
import (
"encoding/json"
"errors"
"io"
"net/http"
)
type OrderRequest struct {
Items []string `json:"items"`
}
func CreateOrderHandler(w http.ResponseWriter, r *http.Request) {
var req OrderRequest
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
// Проверяем EOF - это значит, что тело пустое
if errors.Is(err, io.EOF) {
// Возвращаем корректную 400, а не 500
http.Error(w, "empty body", http.StatusBadRequest)
return
}
// Для остальных ошибок оставляем 500
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
// Дальше бизнес-логика создания заказа...
}
Теперь вы увидите, как это выглядит в тесте:
// handler_test.go
package api
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestCreateOrderHandler_EmptyBodyReturns400(t *testing.T) {
// Создаем HTTP-запрос без тела
req := httptest.NewRequest(http.MethodPost, "/orders", nil)
// httptest.NewRecorder - заглушка для ResponseWriter
rr := httptest.NewRecorder()
// Вызываем хендлер
CreateOrderHandler(rr, req)
// Проверяем, что статус-код 400
if rr.Code != http.StatusBadRequest {
t.Fatalf("expected status %d, got %d", http.StatusBadRequest, rr.Code)
}
}
Этот тест:
- прямо проверяет поведение на пустом теле;
- зафиксирует результат и в будущем не даст случайно вернуть 500;
- легко автоматизируется в CI.
Smoke-тесты после выката
После выката хотфикса в продакшен полезно иметь короткий чек-лист проверок. Например, для e-commerce:
- можно оформить заказ с разными наборами товаров;
- корректно отображается корзина и итоговая сумма;
- оплата проходит и создает правильные транзакции;
- нет всплеска ошибок 4xx/5xx в логах на основных эндпоинтах.
Эти проверки могут быть:
- автоматизированными (через Postman-коллекцию, k6, Cypress);
- ручными (если автоматизации еще нет, но сценариев немного).
Главное — не выкатывать хотфикс полностью «вслепую».
CI/CD и автоматизация хотфиксов
Как организовать отдельный pipeline для hotfix
Во многих командах удобно делать отдельный pipeline для веток hotfix. Например, в GitLab CI:
# .gitlab-ci.yml
stages:
- test
- build
- deploy
# Общие тесты для всех веток
unit_tests:
stage: test
script:
- go test ./...
only:
- branches
# Сборка для хотфиксов
build_hotfix:
stage: build
script:
- go build ./cmd/app
only:
- /^hotfix\/.*$/
# Здесь можно добавить специфические настройки сборки
# Выкат хотфикса на staging
deploy_hotfix_staging:
stage: deploy
script:
- ./deploy.sh staging
only:
- /^hotfix\/.*$/
when: manual
# manual - чтобы вы могли запустить деплой вручную после проверки
# Выкат хотфикса на production
deploy_hotfix_prod:
stage: deploy
script:
- ./deploy.sh production
only:
- tags
# Обычно продакшен завязан на тег релиза хотфикса
Комментарии к этому примеру:
only: /^hotfix/.*$/— job запускается только для веток hotfix;- deploy на staging запускается вручную — вы можете решить, когда готовы;
- deploy на production привязан к тэгам — это дисциплинирует версионирование.
Выкат хотфикса через теги
Многие команды используют правило:
- любой деплой в прод происходит только по тэгу;
- тег привязан к коммиту в main (или другой стабильной ветке).
Пример:
# Убедились, что MR хотфикса влит в main
git checkout main
git pull origin main
# Ставим тег хотфикса
git tag -a v1.4.1 -m "Hotfix 1.4.1 - fix panic on empty cart"
# Отправляем тег в репозиторий
git push origin v1.4.1
Дальше CI:
- видит новый тег
v1.4.1; - запускает pipeline деплоя на production;
- использует артефакты или сборку с того же коммита.
Так вы:
- всегда можете точно сказать, какой код сейчас в проде;
- легко откатиться на предыдущий тег, если нужно.
Версионирование и документация хотфиксов
Семантическое версионирование и hotfix
Если вы используете semver (MAJOR.MINOR.PATCH), то:
- хотфиксы обычно увеличивают PATCH-часть версии;
- пример:
1.4.0→1.4.1→1.4.2и т.д.
Рекомендуемый подход:
- не «прятать» хотфиксы под тем же номером версии;
- каждый хотфикс — отдельная версия, даже если изменения минимальны.
Это помогает:
- однозначно сопоставлять версии артефактов и кода;
- не путаться в истории доката и откатов;
- отслеживать, с какой версии проблема считается исправленной.
Документация: changelog и записи о хотфиксах
Полезно вести краткий changelog, куда вы добавляете запись для каждого хотфикса. Например, в формате Markdown:
## v1.4.1 - 2025-10-18
### Fixed
- Исправлена паника при оформлении заказа с пустой корзиной
- Эндпоинт - POST /checkout
- Причина - отсутствие проверки nil в CalculateFinalPrice
- Тикет - PROD-123
Такая запись:
- экономит время при разборе инцидентов в будущем;
- помогает новым разработчикам быстро понять контекст;
- связывает код, тикет и конкретную версию.
Можно хранить changelog:
- в репозитории (файл CHANGELOG.md);
- в системе документации (Confluence, Notion и т.п.);
- в системе управления инцидентами.
Откат хотфикса и rollback-стратегии
Когда может понадобиться откат хотфикса
Даже хорошо протестированный хотфикс может:
- раскрыть скрытые зависимости;
- вступить в конфликт с другим изменением;
- сработать иначе под реальной нагрузкой.
Если после выката вы видите:
- рост ошибок;
- ухудшение производительности;
- сбои в смежных сервисах;
вам может потребоваться откатиться.
Откат через деплой предыдущего тега
Это самый безопасный и понятный способ. Предположим:
- до хотфикса в проде была версия
v1.4.0; - вы выкатили
v1.4.1как хотфикс; - после выката нашли серьезную проблему.
Если ваш деплой построен на тегах, то вы можете:
# Деплой предыдущего тега (примерный скрипт)
./deploy.sh production v1.4.0
Внутри deploy.sh может быть:
# deploy.sh (очень упрощенный псевдоскрипт)
env=$1 # окружение - staging или production
version=$2 # тег версии
# Скачиваем артефакт нужной версии
# Здесь мы предполагаем, что артефакт собирается и хранится в registry
docker pull registry.example.com/app:${version}
# Выкатываем его в нужное окружение
# Например, обновляем образ в Kubernetes
kubectl set image deployment/app \
app=registry.example.com/app:${version} \
--namespace=${env}
Комментарии:
- вы не «отменяете» коммит хотфикса в репозитории;
- вы просто развертываете в продакшене предыдущую, заведомо стабильную версию;
- затем в спокойном режиме разбираетесь, что пошло не так.
Откат коммита через git revert (когда это нужно)
Бывает, что вам нужно убрать код хотфикса из ветки main или develop. Тогда используйте git revert, а не git reset.
# Ищем хэш коммита хотфикса
git log --oneline
# Допустим, хотфикс - это коммит abc1234
git revert abc1234
# Разрешаем конфликты, если есть, и коммитим
git commit -m "Revert hotfix 1.4.1 due to side effects"
git push origin main
Важно:
revertсоздает новый коммит, который «отменяет» изменения предыдущего;- это сохраняет историю и позволяет понимать, что произошло;
reset --hardиспользовать в таких ситуациях рискованно, он ломает общую историю.
Типичные ошибки при работе с хотфиксами
Ошибка 1. Хотфикс собирают из ветки разработки
Сценарий:
- в develop уже лежат незавершенные фичи;
- для скорости берут код для хотфикса прямо из develop;
- вместе с хотфиксом попадают недотестированные изменения.
Как избежать:
- всегда создавать ветки hotfix от стабильного релиза (тега или main);
- не тащить фичи из develop в хотфикс, если это вообще не критично.
Ошибка 2. Ветку хотфикса забывают смержить обратно
Ситуация:
- хотфикс сделали, выкатили, все работает;
- код есть только в ветке hotfix и в main;
- в develop этот код не попал;
- через несколько недель при мёрже develop в main баг возвращается.
Решение:
- после выката хотфикса в main — обязательно мёржить main в develop;
- иметь простой чек-лист действий после каждого хотфикса (мерж, тег, changelog).
Ошибка 3. Слишком много изменений в хотфиксе
Пример:
- в рамках хотфикса разработчик «заодно»:
- отрефакторил модуль;
- сменил формат логирования;
- подправил несколько нерелевантных багов.
Риск:
- трудно понять, что именно сломало систему, если что-то пойдет не так;
- сложно быстро откатиться частично.
Лучше:
- вынести вокруг-лежащие правки в отдельную задачу и нормальный релиз;
- в хотфиксе делать только то, что критично и связано с инцидентом.
Ошибка 4. Отсутствие тестов вокруг хотфикса
Без тестов:
- легко через несколько релизов снова «сломать» поведение;
- следующий разработчик не поймет, зачем этот странный if вообще нужен.
Практика:
- к каждому хотфиксу по возможности добавлять хотя бы один тест;
- тест должен «ловить» тот самый баг, из-за которого делали хотфикс.
Организация процесса: роли и ответственность
Кто решает, что нужен хотфикс
Обычно решение о запуске хотфикса принимает не один человек, а небольшая группа:
- продакт (или владелец продукта) — оценивает бизнес-эффект;
- технический лидер/тимлид — оценивает сложность исправления и риски;
- SRE/DevOps — оценивают влияние на инфраструктуру и процесс выката.
Полезно иметь четкие критерии, когда запускается хотфикс, а когда — обычный релиз.
Минимальный процесс хотфикса
Можно описать его так:
- Обнаружение инцидента (алерт, заявка от поддержки, лог).
- Быстрая оценка:
- сколько пользователей затронуто;
- влияние на деньги/безопасность;
- можно ли подождать до планового релиза.
- Решение: делаем хотфикс или нет.
- Назначение ответственного разработчика и ревьюера.
- Создание ветки hotfix от стабильной версии.
- Исправление, тесты, ревью.
- Деплой в staging / тестовое окружение.
- Проверки, smoke-тесты.
- Деплой в production.
- Мерж в main (если не оттуда начинали) и develop.
- Документация:
- запись в changelog;
- обновление тикета/инцидента.
Даже такая простая схема уже сильно уменьшает хаос.
Хотфиксы — это мощный инструмент быстрого реагирования на критичные проблемы в продакшене. Основные идеи, которые стоит удерживать:
- создавать ветки hotfix только от стабильного состояния (релиз, тег);
- минимизировать объем изменений и всегда добавлять тесты вокруг исправления;
- автоматизировать выкат через CI/CD и привязку к тегам;
- не забывать мёржить хотфиксы обратно в основную ветку разработки;
- вести прозрачную документацию: какие баги, в каких версиях и как были исправлены.
Если вы выстроите простой, но четкий процесс, хотфиксы перестанут быть «хаосом посреди ночи» и станут управляемым механизмом, который помогает бизнесу продолжать работать даже в сложных ситуациях.
Частозадаваемые технические вопросы по теме хотфиксов
Вопрос 1. Как поступать, если в одном хотфиксе нужно поправить сразу несколько критичных багов
Старайтесь разделять баги по веткам, но если баги сильно пересекаются по коду и их тяжело разделить, то:
- Заводите отдельные задачи в трекере на каждый баг.
- В одной ветке hotfix исправляете все нужные проблемы.
- В коммитах и описании MR явно указываете, какие тикеты закрываются.
- Пишете отдельные тесты под каждый баг, чтобы сохранить прозрачность.
Вопрос 2. Что делать, если хотфикс требует миграции базы данных
Минимизируйте риск:
- По возможности делайте обратимую миграцию (down-скрипт).
- Разделяйте деплой кода и миграций во времени — сначала миграция, затем код, который ее использует.
- Используйте feature-флаг, чтобы включать новый функционал только после проверки миграции.
- Тестируйте миграцию на копии продовой базы или на максимально приближенных данных.
Вопрос 3. Как быть, если хотфикс нужно выкатить только части клиентов (канареечный релиз)
Используйте механизмы «canary release»:
- Настройте балансировщик/ingress так, чтобы часть трафика шла на новый билд.
- Разделите deployment-ы по меткам (например,
app=v1иapp=v1-hotfix). - Контролируйте долю трафика конфигурацией балансировщика.
- Мониторьте метрики и логи отдельно для старой и новой версии.
- Если все стабильно — увеличивайте долю трафика до 100 %.
Вопрос 4. Как обрабатывать хотфиксы в монорепозитории с несколькими сервисами
Рекомендуемый подход:
- Для ветки hotfix меняйте только те сервисы/пакеты, которые реально затронуты багом.
- В CI запускайте только тесты затронутых модулей (можно использовать список измененных файлов для фильтрации).
- Собирайте и деплойте только необходимые сервисы, а не весь монорепозиторий.
- В теге версии указывайте диапазон затронутых сервисов, например
v1.4.1-orders.
Вопрос 5. Как поступать, если хотфикс пересекается по коду с уже открытой feature-веткой
Действуйте так:
- Делайте хотфикс от стабильной ветки (main или релизного тега), а не от feature-ветки.
- После мёржа хотфикса в main — мёржите main в feature-ветку.
- Внимательно решайте конфликты, оставляя логика хотфикса в приоритете.
- Прогоняйте тесты feature-ветки, чтобы убедиться, что новая фича учитывает исправленный баг.
Постройте личный план изучения Git до уровня Middle — бесплатно!
Git — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Git
Лучшие курсы по теме

Основы Git
Антон Ларичев
TypeScript с нуля
Антон Ларичев