GitLab CI DinD: сборка Docker-образов с кэшированием BuildKit

Published: 2026-03-25

Docker-in-Docker (DinD) в GitLab CI позволяет собирать и публиковать контейнерные образы в рамках пайплайна. Настройка включает TLS, сервис Docker daemon и общий том для сертификатов. С кэшированием слоёв BuildKit через registry инкрементальные сборки занимают 30–60 секунд вместо 5 минут.


Базовая настройка DinD

yaml# .gitlab-ci.yml
variables:
  DOCKER_TLS_CERTDIR: "/certs"
  DOCKER_BUILDKIT: "1"
  REGISTRY: registry.example.com

services:
  - name: docker:26-dind
    command: ["--registry-mirror", "https://mirror.example.com"]

build:
  image: docker:26
  stage: build
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$REGISTRY"
  script:
    - |
      docker buildx build \
        --cache-from type=registry,ref=${REGISTRY}/${CI_PROJECT_NAME}:cache \
        --cache-to type=registry,ref=${REGISTRY}/${CI_PROJECT_NAME}:cache,mode=max \
        --platform linux/amd64 \
        --tag ${REGISTRY}/${CI_PROJECT_NAME}:${CI_COMMIT_SHORT_SHA} \
        --tag ${REGISTRY}/${CI_PROJECT_NAME}:latest \
        --push \
        .

Сервис docker:dind и образ docker:26 должны использовать одну мажорную версию. DOCKER_TLS_CERTDIR: "/certs" включает TLS между клиентом и daemon автоматически.


Проблема с TLS-сертификатами

Без общего DOCKER_TLS_CERTDIR Docker CLI не может подключиться к daemon:

Cannot connect to the Docker daemon at tcp://docker:2376.

Решение: DOCKER_TLS_CERTDIR: "/certs" — GitLab автоматически монтирует том между сервисом и контейнером задачи. Оба видят /certs.

Для отладки без TLS: DOCKER_TLS_CERTDIR: "" и tcp://docker:2375 — только не в production.


Registry mirror

Registry mirror — pull-through кэш для Docker Hub. Без него каждый DinD-runner тянет alpine:3.20 напрямую с Docker Hub, упираясь в rate limit и добавляя 30–60 секунд.

yamlservices:
  - name: docker:26-dind
    command: ["--registry-mirror", "https://mirror.example.com"]

mirror.example.com — собственный Registry proxy (registry:2 с proxy-конфигом):

yamlproxy:
  remoteurl: https://registry-1.docker.io

Кэширование BuildKit: inline или registry

inline — встраивает метаданные кэша в слой образа. Просто, но вынуждает тянуть весь образ.

registry — хранит кэш как отдельный манифест в registry. Тег cache маленький (только метаданные + diff). mode=max кэширует все стадии сборки, а не только финальную.

Для multi-stage-сборки .NET:

  • restore меняется редко (только при изменении .csproj)
  • build меняется каждый коммит
  • С mode=max стадия restore берётся из кэша даже на раннерах без локального кэша

Multi-arch сборки

yamlbefore_script:
  - docker run --privileged --rm tonistiigi/binfmt --install all
  - docker buildx create --use --name multiarch

script:
  - |
    docker buildx build \
      --platform linux/amd64,linux/arm64 \
      --cache-from type=registry,ref=${REGISTRY}/${CI_PROJECT_NAME}:cache \
      --cache-to type=registry,ref=${REGISTRY}/${CI_PROJECT_NAME}:cache,mode=max \
      --tag ${REGISTRY}/${CI_PROJECT_NAME}:${CI_COMMIT_SHORT_SHA} \
      --push \
      .

binfmt регистрирует QEMU-эмуляторы. Сборка под эмулируемую архитектуру идёт в 3–5 раз медленнее.


Сканирование Trivy в том же пайплайне

yamlscan:
  image:
    name: aquasec/trivy:latest
    entrypoint: [""]
  stage: scan
  needs: [build]
  script:
    - |
      trivy image \
        --format json \
        --output trivy-report.json \
        --severity HIGH,CRITICAL \
        --exit-code 1 \
        ${REGISTRY}/${CI_PROJECT_NAME}:${CI_COMMIT_SHORT_SHA}
  artifacts:
    when: always
    reports:
      container_scanning: trivy-report.json

--exit-code 1 — пайплайн падает при HIGH/CRITICAL уязвимостях. Артефакт container_scanning отображается в MR во вкладке Security.


Kaniko как альтернатива

Если привилегированные контейнеры недоступны (так бывает в части Kubernetes-инсталляций):

yamlbuild:
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  script:
    - |
      /kaniko/executor \
        --context . \
        --dockerfile Dockerfile \
        --destination ${REGISTRY}/${CI_PROJECT_NAME}:${CI_COMMIT_SHORT_SHA} \
        --cache=true \
        --cache-repo=${REGISTRY}/${CI_PROJECT_NAME}:cache

Kaniko не требует privileged mode. Компромисс: работает медленнее BuildKit и не поддерживает multi-arch.