Олег Марков
Теги версий version tags в Git и Docker
Введение
Теги версий (version tags) — это способ привязать понятную человеку метку к определенному состоянию кода, артефакта сборки или контейнера. Они помогают ответить на вопросы вида:
- Какая версия сейчас в продакшене
- На основе какого коммита собран этот образ
- Где находится стабильная версия, а где — экспериментальная
Смотрите, я покажу вам, как теги версий упрощают жизнь в нескольких типичных сценариях:
- вы используете Git и хотите зафиксировать релиз 1.0.0;
- вы публикуете Docker-образы и должны поддерживать latest, stable и конкретные версии;
- вы настраиваете CI/CD и вам нужно однозначно помечать релизные сборки.
Давайте разберем, что такое теги версий, как их правильно придумывать, где хранить и как сделать так, чтобы они действительно помогали, а не путали команду.
Закрепите тему на практике: Курс по Git с поддержкой наставников.
Курс по теме
Курс по Git
40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней
Что такое тег версии
Основное определение
Тег версии — это текстовая метка, которую вы присваиваете:
- коммиту в системе контроля версий (чаще всего в Git);
- артефакту сборки (например, архиву с бинарником);
- образу контейнера (например, Docker image);
- пакету в репозитории (npm, PyPI, Maven, Go Module и т.д.).
Главная идея — связать человеческое представление о версии с конкретным техническим артефактом. Вместо «коммит 7f9d2e3» вы говорите «версия 1.4.2».
Чаще всего в тегах версий используется семантическое версионирование: формат MAJOR.MINOR.PATCH (например, 2.5.1). Но на практике применяют и другие схемы — с датами, суффиксами среды (-dev, -prod), хешами, build-номерами.
Семантическое версионирование как основа тегов
Что такое семантическое версионирование
Семантическое версионирование (SemVer) — это договоренность, как читать номер версии и что он означает. В классическом виде:
- MAJOR — мажорная версия (несовместимые изменения);
- MINOR — минорная (новые возможности, совместимые по API);
- PATCH — патч-версия (исправления ошибок, без изменения контракта).
Например, версия 3.2.7 содержит:
- 3 — третье поколение API, может быть несовместимо с 2.x.x;
- 2 — в этой мажорной ветке уже был добавлен второй пакет изменений;
- 7 — это седьмой набор исправлений к версии 3.2.0.
Когда увеличивать какую часть версии
Давайте разберемся на примере типичных изменений:
Изменили формат запроса в API, удалили поле, поломали обратную совместимость:
- увеличиваем MAJOR: 1.4.5 → 2.0.0.
Добавили новое поле в ответ API, которое не ломает существующих клиентов:
- увеличиваем MINOR: 1.4.5 → 1.5.0.
Исправили баг, не меняя контракта, добавили логирование:
- увеличиваем PATCH: 1.4.5 → 1.4.6.
Семантическое версионирование удобно тем, что по номеру версии уже можно примерно понять, насколько опасен апдейт.
Теги версий в Git
Зачем тегировать коммиты
Если вы используете Git, теги — стандартный механизм пометить конкретный коммит как релизную версию. Это нужно:
- чтобы можно было быстро вернуться к коду релиза 1.2.0;
- чтобы CI/CD понимал, какие коммиты — релизные;
- чтобы автоматически собирать changelog между версиями;
- чтобы при расследовании инцидента знать, какой именно код был в продакшене.
Виды Git-тегов
В Git есть два типа тегов:
- lightweight tag (легковесный) — просто указатель на коммит;
- annotated tag (аннотированный) — тег с метаданными: автор, дата, сообщение, подписи.
Пример создания легковесного тега
# Помечаем текущий коммит тегом версии 1.0.0
git tag v1.0.0Здесь мы создаем тег без дополнительной информации. Такой формат подходит для простых локальных пометок, но хуже для релизов.
Пример создания аннотированного тега
# Создаем аннотированный тег с сообщением
git tag -a v1.0.0 -m "Релиз первой стабильной версии"# Просмотр информации о теге и связанном коммите
git show v1.0.0Комментарии в этом примере подсказывают вам, какие команды использовать в реальной работе.
Рекомендация: для релизных тегов используйте именно аннотированные теги. Они лучше документируют историю и могут быть подписаны GPG.
Как назначать теги на разные коммиты
Иногда вам нужно создать тег не на текущий коммит, а на один из прошлых. Давайте посмотрим, как это сделать.
# Создаем тег на конкретный коммит по его хешу
git tag -a v1.1.0 7f9d2e3 -m "Релиз 1.1.0 на основе коммита 7f9d2e3"Если вы не уверены в нужном хеше, можно сначала посмотреть лог:
# Показываем сокращенную историю коммитов
git log --oneline --decorate --graph --allЗдесь вы увидите коммиты и уже существующие теги, что упростит навигацию.
Работа с удаленными тегами
Локального тега мало — его нужно отправить в удаленный репозиторий, чтобы его видела вся команда и CI/CD.
# Отправить один тег на удаленный репозиторий origin
git push origin v1.0.0
# Отправить все локальные теги сразу
git push origin --tagsЕсли понадобится удалить тег:
# Удаляем тег локально
git tag -d v1.0.0
# Удаляем тег в удаленном репозитории
git push origin :refs/tags/v1.0.0Обратите внимание, что удаление тегов, на которые кто-то ссылается в документации или скриптах сборки, может привести к путанице. Старайтесь удалять только те теги, которые были созданы по ошибке.
Теги версий в Docker
Зачем тегировать Docker-образы
Docker-образы часто используют несколько тегов для одного и того же образа. Это позволяет:
- иметь стабильный тег latest для последней версии;
- иметь тег с конкретной версией, например 1.3.0;
- иметь тег, привязанный к конкретному коммиту или сборке, например 1.3.0-7f9d2e3.
Теги помогают вам понять, какой именно образ сейчас крутится в кластере, и упростить откат.
Базовые операции с тегами Docker
Теперь вы увидите, как это выглядит в коде командной строки.
# Собираем образ и даем ему тег myapp:1.0.0
docker build -t myapp:1.0.0 .
# Публикуем образ в Docker Registry
docker push myapp:1.0.0
# Создаем дополнительный тег latest для того же образа
docker tag myapp:1.0.0 myapp:latest
# Публикуем тег latest
docker push myapp:latestКомментарии рядом с командами подсказывают, что именно происходит на каждом шаге.
Фактически Docker тег — это просто человекочитаемый алиас к одному и тому же image ID. Один образ может иметь несколько тегов.
Схемы тегирования Docker-образов
Давайте разберемся на примере нескольких распространенных схем.
Схема 1: только семантическая версия
- myapp:1.0.0
- myapp:1.0.1
Плюсы:
- просто и понятно;
- легко связать версию образа с версией кода.
Минусы:
- вы не видите, какой именно коммит был собран;
- при пересборке «той же» версии будет трудно отличить старую и новую сборки.
Схема 2: версия + commit hash
- myapp:1.0.0-7f9d2e3
- myapp:1.0.1-a8c1f45
Покажу вам, как это реализовано на практике с помощью аргумента сборки:
# Строим образ и добавляем в тег версию и хеш коммита
# Предполагаем, что переменные VERSION и GIT_SHA определены заранее
docker build -t myapp:${VERSION}-${GIT_SHA} .
# Пример значений
# VERSION=1.0.0
# GIT_SHA=7f9d2e3Такая схема дает более точную привязку образа к состоянию репозитория.
Схема 3: stable, latest и окружения
Дополнительно к версионным тегам можно ввести логические:
- myapp:latest — последняя собранная версия;
- myapp:stable — последняя проверенная и одобренная версия;
- myapp:prod, myapp:staging — образы, которые использует конкретное окружение.
Например:
# Помечаем текущую стабильную версию тегами stable и prod
docker tag myapp:1.2.3 myapp:stable
docker tag myapp:1.2.3 myapp:prod
docker push myapp:stable
docker push myapp:prodОбратите внимание, что такие теги — это всегда указатели. Их можно перепривязывать к новым версиям, не меняя манифестов деплоя (если там используются логические теги).
Связь Git-тегов и Docker-тегов
Зачем связывать Git и Docker
Если вы помечаете в Git релиз тегом v1.2.3 и затем собираете Docker-образ с тегом 1.2.3, вы получаете прозрачную линию:
Git tag → CI сборка → Docker image tag → продакшен.
Это упрощает:
- поиск кода, который сейчас крутится;
- анализ регрессий;
- аудит релизов.
Пример простого пайплайна
Давайте разберемся на примере упрощенного сценария:
- Вы создаете в Git тег v1.2.3.
- CI запускает сборку при появлении тега.
- CI собирает Docker-образ и помечает его тегами:
- myapp:1.2.3
- myapp:1.2.3-
<short_sha> - myapp:stable (по решению)
Ниже приведен псевдокод для CI-конвейера (синтаксис близок к GitLab CI):
# Джоб, который запускается только при пуше тега
build_and_push:
stage: build
# Здесь мы говорим, что задача запускается при пуше тегов
only:
- tags
script:
# Извлекаем версию из имени тега, например v1.2.3 -> 1.2.3
- VERSION=${CI_COMMIT_TAG#v}
# Собираем образ с тегом версии
- docker build -t registry.example.com/myapp:${VERSION} .
# Добавляем тег с сокращенным хешем коммита
- docker tag registry.example.com/myapp:${VERSION} \
registry.example.com/myapp:${VERSION}-${CI_COMMIT_SHORT_SHA}
# Публикуем оба тега
- docker push registry.example.com/myapp:${VERSION}
- docker push registry.example.com/myapp:${VERSION}-${CI_COMMIT_SHORT_SHA}Комментарии помогают вам увидеть, где именно извлекается версия и как формируются теги.
Стратегии тегирования в проектах
Базовая стратегия для небольших проектов
Если у вас небольшой сервис или библиотека, можно начать с простой схемы:
- В Git:
- использовать аннотированные теги вида vMAJOR.MINOR.PATCH;
- В Docker:
- собирать образы с тегом MAJOR.MINOR.PATCH;
- при необходимости добавлять latest для последней версии.
Пример:
- Git: v1.0.0
- Docker: myapp:1.0.0, myapp:latest
При релизе 1.1.0:
- Git: v1.1.0
- Docker: myapp:1.1.0, myapp:latest
Стратегия для микросервисной архитектуры
Когда сервисов много, теги версий особенно важны. Здесь полезно:
- строго соблюдать SemVer;
- для каждого сервиса иметь свой набор тегов;
- в Kubernetes-манифестах использовать «неподвижные» теги с версией, а не latest.
Например, деплой манифеста:
# Фрагмент Deployment для Kubernetes
spec:
template:
spec:
containers:
- name: myapp
# Здесь мы используем конкретный тег версии
image: registry.example.com/myapp:1.4.2Комментарий к полю image подчеркивает, что мы избегаем latest, чтобы деплой был детерминированным.
После тестирования вы можете обновить этот тег на новый:
image: registry.example.com/myapp:1.5.0Это позволяет четко отследить, на какой версии произошла проблема.
Работа с тегами версий в пакетных менеджерах
npm (JavaScript, Node.js)
В экосистеме Node.js версия пакета задается в файле package.json. Тег в Git часто синхронизируют с этой версией.
Фрагмент package.json:
{
"name": "my-lib",
"version": "1.2.3"
}# Публикуем пакет в npm с указанной версией
npm publishКогда вы повышаете версию:
# Автоматически увеличиваем patch-версию и создаем Git-тег
npm version patch
# Автоматически увеличиваем minor-версию
npm version minor
# Автоматически увеличиваем major-версию
npm version majorКаждая команда:
- обновляет поле version в package.json;
- создает коммит;
- создает соответствующий Git-тег (например, v1.2.4).
Здесь вы видите, как инструмент сам поддерживает связь между кодом и тегом.
PyPI (Python)
В Python версия обычно задается в pyproject.toml или setup.cfg. Соглашения похожие: версия пакета = тег в Git.
Фрагмент pyproject.toml:
[project]
name = "my-lib"
version = "0.3.1"Часто используют инструменты вроде setuptools_scm, которые автоматически берут версию из Git-тегов. Это еще сильнее привязывает понятие «версии» к тегам.
Автоматизация тегирования в CI/CD
Автоинкремент версий
Давайте посмотрим, как можно автоматически увеличивать номер версии на основе изменений.
Идея:
- анализировать сообщения коммитов;
- по ключевым словам решать, что увеличивать (MAJOR, MINOR, PATCH);
- генерировать новый тег версии.
Например, принятое соглашение по commit messages:
feat:— новая функциональность (MINOR);fix:— исправление (PATCH);BREAKING CHANGEв описании — MAJOR.
Специализированные инструменты (например, semantic-release) позволяют:
- прочитать историю коммитов после последнего тега;
- вычислить новую версию;
- создать Git-тег;
- опубликовать релиз и артефакты.
Использование build-номеров
Иногда к версиям добавляют build-номер:
- 1.2.3+45
- 1.2.3-build.45
Это удобно, когда:
- у вас бывают несколько сборок одной версии (например, инженеры тестируют разные варианты);
- нужно связать релиз с конкретной сборкой CI.
Пример формирования версии в CI (псевдокод):
# Берем базовую версию из файла
BASE_VERSION=$(cat VERSION)
# Используем номер пайплайна CI как build-номер
BUILD_NUM=${CI_PIPELINE_ID}
# Формируем полную версию
FULL_VERSION="${BASE_VERSION}+${BUILD_NUM}"
echo "Полная версия сборки: ${FULL_VERSION}"Комментарий в выводе подсказывает, как выглядит итоговая версия.
Хорошие практики работы с тегами версий
1. Не переиспользовать теги
Если вы уже выпустили версию 1.0.0 и кто-то ее использует, не стоит:
- пересобирать код;
- заново пушить тег 1.0.0 на другой коммит.
Это может привести к ситуациям, когда под одной и той же версией понимаются разные состояния. Лучше:
- выпускать 1.0.1;
- или использовать дополнительный суффикс (1.0.0-1, 1.0.0-hotfix).
2. Фиксировать версию в коде и документации
Хорошая практика — хранить версию не только в тегах, но и в коде:
- в константе (для сервисов);
- в файле VERSION;
- в package.json, pyproject.toml и аналогичных файлах.
Тогда сервис может, например, отображать версию в /health или /version эндпоинтах, а вы сможете быстро сравнить:
- версию тега;
- версию Docker-образа;
- версию, возвращаемую сервисом.
3. Единый формат именования
Старайтесь не смешивать форматы:
- v1.2.3 и 1.2.3;
- release-1.2.3 и 1.2.3.
Выберите один формат, например vMAJOR.MINOR.PATCH для Git и MAJOR.MINOR.PATCH для Docker, и придерживайтесь его.
Это снизит риск ошибок в скриптах и пайплайнах, где вы парсите версию.
4. Логические теги использовать аккуратно
Теги вроде latest, stable, prod удобны, но:
- они «плавающие» — сегодня указывают на одну версию, завтра на другую;
- трудно восстановить историю, если вы не ведете дополнительный журнал.
Хороший компромисс:
- в продакшене использовать зафиксированные теги (1.3.0);
- логические теги применять в тестовых и временных окружениях;
- обязательно логировать, каким именно тегом был развернут релиз.
Типичные ошибки и как их избежать
Ошибка 1. Использование latest в продакшене
Если вы в деплое указываете:
image: myapp:latestто каждый новый push latest может незаметно менять поведение продакшена. Это усложняет отладку и откаты.
Решение:
- в продакшене использовать только конкретные версии: 1.2.3;
- latest использовать только локально или в тестовых окружениях.
Ошибка 2. Беспорядочные версии
Когда разработчики ставят версии «на глаз»:
- 1.0, 1.1, 1.1.1, 1.1.1fix;
- 2023.01, 2023.1.2-prod.
Это затрудняет анализ и автоматизацию. Рекомендация:
- выбрать одну договоренность (SemVer или дата+build);
- документировать ее в CONTRIBUTING или README;
- на CI проверять корректность формата версии.
Ошибка 3. Отсутствие связи между тегом и изменениями
Если вы создаете тег 1.2.3, но нигде не описываете, что в него вошло, поддерживать проект становится сложно.
Решение:
- вести CHANGELOG (вручную или автоматически);
- в Git-теге оставлять понятное описание релиза;
- использовать релизные заметки в GitHub, GitLab и т.п.
Заключение
Теги версий — это базовый, но очень важный инструмент управления жизненным циклом программного обеспечения. Они связывают:
- код (Git-коммиты);
- артефакты сборки (пакеты, бинарники);
- окружения (Docker-образы, деплои).
При продуманной схеме тегирования вы всегда можете ответить:
- какой код сейчас в продакшене;
- какие изменения были между 1.2.0 и 1.3.0;
- к какому коммиту относится конкретный образ.
Ключевые элементы:
- использовать понятную схему версий (чаще всего SemVer);
- обязательно тегировать релизы в Git;
- синхронизировать Git-теги с тегами Docker и пакетных менеджеров;
- автоматизировать создание и проверку тегов в CI/CD.
Чем раньше вы введете в проекте ясные правила тегирования версий, тем проще будет масштабировать разработку, сопровождение и деплой.
Частозадаваемые технические вопросы
1. Как получить последнюю версию по тегам в Git из скрипта
Можно отсортировать теги по версии и взять последний:
# Выводим последний тег по сортировке версий
git describe --tags --abbrev=0Если вам нужна сортировка по семантическим версиям, а теги записаны в формате vMAJOR.MINOR.PATCH, можно использовать:
# Получаем последний тег в формате vX.Y.Z
git tag --list "v*" --sort=-version:refname | head -n 1Комментарии здесь не нужны, команда и так читается достаточно явно.
2. Как проверить в CI, что тег версии соответствует формату SemVer
Вы можете использовать простое регулярное выражение:
# Проверяем, что тег выглядит как vX.Y.Z
echo "${CI_COMMIT_TAG}" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'Если команда grep вернет ненулевой код выхода, значит тег не соответствует формату, и можно прервать пайплайн.
3. Как автоматически проставлять Docker-теги с датой сборки
В скрипте сборки можно добавить дату:
# Формируем тег вида 1.2.3-20251204
DATE_TAG=$(date +%Y%m%d)
VERSION=1.2.3
docker build -t myapp:${VERSION}-${DATE_TAG} .Такой тег поможет быстро понять, когда был собран образ.
4. Как связать версию из файла и Git-тег в проверке
Частая задача — убедиться, что версия из файла совпадает с Git-тегом. Можно сделать так:
# Читаем версию из файла VERSION
FILE_VERSION=$(cat VERSION)
# Удаляем префикс v из тега
TAG_VERSION=${CI_COMMIT_TAG#v}
test "${FILE_VERSION}" = "${TAG_VERSION}"Если сравнение не пройдет, пайплайн можно остановить, чтобы не выпускать «ломаный» релиз.
5. Как пометить hotfix к старой версии без нарушения SemVer
Если вам нужно выпустить фикc для старой ветки, увеличивайте PATCH в рамках этой ветки:
- было 1.4.2 (старая ветка);
- основная ветка уже ушла на 2.x;
- hotfix — это 1.4.3.
Git-тег будет v1.4.3, Docker-тег — 1.4.3. Такой подход сохраняет прозрачность версий и не ломает SemVer.
Постройте личный план изучения Git до уровня Middle — бесплатно!
Git — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Git
Лучшие курсы по теме

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