Golden builder image и Docker Registry: ускоряем сборку и публикуем образы

Когда нужен golden builder image, как его сопровождать и как работать с Docker Registry вне Docker Hub

Когда в проекте много сервисов, один хороший Dockerfile уже не решает все проблемы. В этот момент на первый план выходят две темы: ускорение сборки через golden builder image и аккуратная публикация образов в registry.

Если базовый Dockerfile ещё не выстроен, начните со статьи про multi-stage сборку, pinning и runtime-безопасность. А если нужна именно дисциплина вокруг CI, secrets, proxy и HEALTHCHECK, рядом есть отдельный материал про production-сборку Docker-образов.

В статье

Когда нужен golden builder image

Golden builder image особенно уместен, если:

  1. У вас много сервисов на одном стеке и они собираются одинаковым набором инструментов.
  2. CI тормозит из-за повторной установки SDK, компиляторов, package managers и системных зависимостей.
  3. Нужны стабильные и воспроизводимые сборки с одинаковым builder-окружением между репозиториями.

Для небольшого проекта с одним-двумя сервисами это может быть лишним оверхедом: отдельный образ, отдельный pipeline и сопровождение не всегда окупаются.

Что важно не перепутать

  1. Golden builder image используется только на этапе сборки, а не как runtime-образ.
  2. Образ должен быть пинован по версиям и регулярно обновляться, включая security patches.
  3. Для него нужен отдельный pipeline: сборка, сканирование, подпись и публикация.
  4. Ускорение достигается за счёт предустановленных инструментов и кэшей, но секреты нельзя запекать внутрь образа.

Пример Dockerfile для прикладного сервиса:

# Пинуем golden builder по версии и digest — гарантия воспроизводимости
FROM registry.company.local/platform/go-builder:1.26.3@sha256:<digest> AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o server ./cmd/server

# Runtime-стадия минимальна — toolchain остался в builder
FROM alpine:3.23
RUN apk --no-cache add ca-certificates && addgroup -S app && adduser -S app -G app
COPY --from=builder /src/server /usr/local/bin/server
USER app
CMD ["server"]

Важно не превращать golden image в “помойку” из инструментов. Он должен быть минимальным, версионируемым и обновляться по расписанию.

CI-паттерн для golden builder image

Обычно это отдельный репозиторий или отдельная директория с Dockerfile builder-образа:

name: golden-builder
on:
  push:
    branches: [main]
    paths:
      - '.github/workflows/golden-builder.yml'
      - 'build-images/go-builder/**'
  schedule:
    - cron: '0 3 * * 1'  # еженедельная пересборка по понедельникам — подхватывает security patches

jobs:
  build-scan-sign-push:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build builder image
        run: docker build -t registry.company.local/platform/go-builder:1.26.3 ./build-images/go-builder

      - name: Scan builder image  # блокируем публикацию при уязвимостях
        run: trivy image --exit-code 1 --severity HIGH,CRITICAL registry.company.local/platform/go-builder:1.26.3

      - name: Push builder image
        run: docker push registry.company.local/platform/go-builder:1.26.3

      - name: Sign builder image  # cosign подписывает образ — можно верифицировать при pull
        run: cosign sign --yes registry.company.local/platform/go-builder:1.26.3

После этого прикладные репозитории используют образ только на этапе сборки и не дублируют установку toolchain в каждом CI job.

Docker Hub не обязателен

Рабочая схема одинакова для любых registry: Harbor, Nexus, GitLab Registry, GHCR, ECR и других. Важна не конкретная платформа, а дисциплина публикации и именования образов.

Как логиниться в registry

Базовый вариант:

# --password-stdin: пароль не попадает в историю shell и ps
echo "$REGISTRY_TOKEN" | docker login registry.company.local -u "$REGISTRY_USER" --password-stdin

Пароль или токен передавайте через stdin, а не в явном виде в командной строке.

Примеры:

echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GITHUB_USER" --password-stdin
echo "$HARBOR_TOKEN" | docker login registry.company.local -u "$HARBOR_USER" --password-stdin

Тегирование и push

# Immutable тег — конкретная версия, не перезаписывается
docker tag app:multi registry.company.local/team/app:1.4.2
# Плавающий тег — указывает на текущий рекомендуемый образ
docker tag app:multi registry.company.local/team/app:stable
docker push registry.company.local/team/app:1.4.2
docker push registry.company.local/team/app:stable

Практика простая: immutable тег, например 1.4.2 или commit sha, плюс удобный плавающий тег вроде stable или latest.

Если образ публикуется в несколько registry, лучше сохранять единый version tag и единый контроль digest в pipeline.

Как перенести образ из одного registry в другой

Самый понятный вариант — через локальный pull, tag и push:

# Простой перенос: pull → переименование → push в другой registry
docker pull registry-a.local/team/app:1.4.2
docker tag registry-a.local/team/app:1.4.2 registry-b.local/team/app:1.4.2
docker push registry-b.local/team/app:1.4.2

Это рабочий вариант, когда нужен простой способ забрать и переложить образ без отдельного инструмента репликации.

Как сохранить образ в архив и перенести в другое окружение

# Экспорт в tar-архив — для переноса на изолированные площадки
docker save registry-a.local/team/app:1.4.2 -o app_1.4.2.tar
# На целевом хосте: загрузка из архива
docker load -i app_1.4.2.tar
docker tag registry-a.local/team/app:1.4.2 registry-b.local/team/app:1.4.2
docker push registry-b.local/team/app:1.4.2

Подход полезен для изолированных контуров, air-gapped окружений или ручного переноса между площадками.

Копирование между registry без локальной загрузки

Для больших образов и автоматизации удобнее использовать skopeo:

# Прямое копирование между registry — без промежуточного docker pull/push
skopeo copy docker://registry-a.local/team/app:1.4.2 docker://registry-b.local/team/app:1.4.2

Это особенно удобно, когда нужно синхронизировать registry без прогонки образа через локальный Docker daemon.

Вывод

Golden builder image и нормальная работа с registry становятся полезны не в момент “первого контейнера”, а когда проект начинает расти. Если сервисов мало, это может быть лишней сложностью. Но для командной разработки и длинных CI-сборок такие практики быстро окупаются.

Чтобы эти практики действительно работали, их лучше строить поверх уже нормальной базы: multi-stage Dockerfile и production-процесса со сканированием, secrets и healthcheck.

Обсуждение в Telegram

Присоединиться →

Комментарии