Сборка образов в CI/CD
Как автоматически собирать и публиковать образы из пайплайна вместо ручного docker build на ноутбуке.
CI/CD — автоматический конвейер: на каждый push в репозиторий система сама собирает образ, прогоняет проверки и публикует артефакт в реестр, без участия человека.
Ручная сборка на ноутбуке плоха тем, что она невоспроизводима и зависит от человека: забыл тег, собрал не из той ветки, на машине другая версия инструментов. CI/CD убирает этот фактор. Каждый push запускает свежую сборку в чистом окружении, образ тегируется детерминированно по коммиту, и в реестр уезжает ровно то, что прошло проверки. Разберём это на двух самых распространённых системах — GitHub Actions и GitLab CI.
Зачем это на практике
Пайплайн — это гарантия, что образ в реестре соответствует конкретному состоянию кода. Появился новый коммит — появился новый образ с тегом этого коммита. Откатиться к прошлому деплою = взять образ прошлого коммита. Никаких «а кто и когда это собирал»: сборка задокументирована логами CI и привязана к git-истории.
Сборка и push в GitHub Actions
В GitHub Actions пайплайн описывается YAML-файлом в .github/workflows/. Готовые actions берут на себя логин в реестр и сборку с кэшем.
name: build-image
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: ghcr.io/acme/api:sha-${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
Здесь secrets.GITHUB_TOKEN — автоматический токен пайплайна, его не нужно заводить руками. Тег sha-${{ github.sha }} привязывает образ к конкретному коммиту. Строки cache-from/cache-to с type=gha включают кэш слоёв — о нём ниже.
Сборка и push в GitLab CI
В GitLab пайплайн описывается в .gitlab-ci.yml. Реестр и токен GitLab подставляет через встроенные переменные CI_REGISTRY*.
build:
stage: build
image: docker:27
services:
- docker:27-dind
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
- docker build -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" .
- docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA"
Переменная $CI_REGISTRY_IMAGE — это адрес репозитория образов проекта, а $CI_COMMIT_SHORT_SHA — короткий хеш коммита. Сервис docker:dind (Docker-in-Docker) даёт демон Docker внутри job, чтобы было где собирать.
Кэш слоёв в CI
Главная боль CI-сборки: каждый запуск стартует в чистом окружении, без локального кэша слоёв. Без кэша Docker заново выполняет apt install, pip install, npm ci при каждой сборке — это минуты впустую. Решение — внешний кэш: Docker сохраняет слои в реестр или в хранилище CI и переиспользует их в следующем запуске.
В GitHub Actions это делают строки cache-from/cache-to: type=gha (показаны выше) — слои кладутся в кэш самого GitHub. Универсальный способ для любого CI — registry-кэш: слои хранятся в реестре рядом с образом.
docker buildx build \
--cache-from type=registry,ref=ghcr.io/acme/api:buildcache \
--cache-to type=registry,ref=ghcr.io/acme/api:buildcache,mode=max \
-t ghcr.io/acme/api:sha-abc123 --push .
Чтобы кэш реально срабатывал, помогает грамотный порядок инструкций в Dockerfile: сначала копировать файлы зависимостей и ставить их, и только потом — исходники. Тогда правка кода не инвалидирует слой с установленными зависимостями.
Тегирование по коммиту и версии
В CI обычно навешивают несколько тегов на один образ — разные теги служат разным целям.
| Тег | Источник | Зачем |
sha-a1b2c3d | хеш коммита | точная привязка к коду, иммутабелен — для деплоя и отката |
1.4.2 | git-тег релиза | человекочитаемая версия релиза |
main | имя ветки | «последний с этой ветки» — для тестовых сред |
Связка коммит-тегов и версий устроена так: сборка из ветки main получает теги sha-… и main, а когда вы ставите git-тег v1.4.2, отдельный триггер собирает образ с тегом 1.4.2. В прод деплоят иммутабельный sha-… или 1.4.2, но никогда — main или latest (см. урок про реестры).
Build matrix: одна сборка — много вариантов
Build matrix — механизм CI, который запускает одну и ту же сборку параллельно с разными параметрами. Классический случай — мультиплатформенные образы (под amd64 и arm64, чтобы образ работал и на обычных серверах, и на ARM/Apple Silicon).
- name: Build multi-arch
uses: docker/build-push-action@v6
with:
push: true
platforms: linux/amd64,linux/arm64
tags: ghcr.io/acme/api:sha-${{ github.sha }}
Так из одного описания получается образ, который Docker сам отдаст в нужной архитектуре при pull. Matrix также используют, чтобы собрать вариации под разные версии рантайма или с разными build-аргументами — параллельно и из одного конфига.
Как это работает под капотом
За современной CI-сборкой стоит BuildKit — движок сборки Docker нового поколения (он же мотор у docker buildx и build-push-action). BuildKit строит из Dockerfile граф зависимостей шагов и выполняет независимые ветки параллельно, а результаты — слои, адресуемые по хешу содержимого, — умеет экспортировать во внешний кэш (cache-to) и импортировать обратно (cache-from). При следующем запуске BuildKit сверяет хеши входов каждого шага: если ничего не поменялось, он не пересобирает слой, а тянет готовый из кэша. Мультиплатформенность он реализует через эмуляцию (QEMU) или нативные раннеры под каждую архитектуру, собирая отдельный образ на платформу и связывая их единым манифест-листом под одним тегом.
Частые ошибки
- Сборка без кэша. Каждый пайплайн заново качает и ставит зависимости — минуты на ветер. Включите
cache-from/cache-to. - Деплой по
main/latest. Эти теги плавающие. В прод — только иммутабельныйsha-или версия. - Секреты в YAML открытым текстом. Пароль реестра должен жить в secrets/CI-переменных, а не в коммите.
- Плохой порядок в Dockerfile.
COPY . .до установки зависимостей сбивает кэш на каждом изменении кода. - Забыли права на push. В GitHub Actions без
permissions: packages: writeпубликация в GHCR упадёт.
Итоги
- CI/CD собирает и публикует образ автоматически на каждый push, в чистом воспроизводимом окружении.
- GitHub Actions использует
docker/build-push-action, GitLab CI —docker build/pushсо встроеннымиCI_REGISTRY*. - Кэш слоёв (
type=ghaили registry-кэш) экономит минуты на каждой сборке; помогает правильный порядок Dockerfile. - Тегируйте по коммиту (
sha-…) и версии; в прод — иммутабельные теги. - Build matrix собирает мультиплатформенные образы (
amd64+arm64) из одного конфига.