Традиционный способ сборки Docker-образов с помощью команды docker build прост и понятен, но при работе с комплексными приложениями, состоящими из множества компонентов, этот процесс может стать утомительным и подверженным ошибкам. Именно здесь на помощь приходит Docker Bake — мощный и гибкий инструмент для организации многоступенчатой и параллельной сборки образов.
В этой статье мы рассмотрим возможности Docker Bake, его преимущества перед стандартным подходом, а также разберем практические примеры использования для различных сценариев разработки.
Что такое Docker Bake
Docker Bake — это функция BuildKit, которая позволяет организовать и автоматизировать процесс сборки Docker-образов с использованием конфигурационных файлов.
Основные преимущества Docker Bake:
Декларативный синтаксис: Вместо множества команд в скриптах вы описываете желаемый результат в HCL (HashiCorp Configuration Language), JSON или YAML(Docker Compose файлы).
Параллельная сборка: BuildKit автоматически выполняет сборку образов параллельно, где это возможно.
Переиспользование кэша: Эффективное использование кэша между различными сборками.
Группировка и целевые сборки: Возможность определять группы образов и собирать только нужные в данный момент цели.
Переменные и наследование: Мощная система переменных и наследования свойств между целями сборки.
Интеграция с CI/CD: Легко встраивается в пайплайны непрерывной интеграции и доставки.
Самый просто пример для понимания, что такое bake, возьмем команду для сборки образа:
$ docker build -f Dockerfile -t myapp:latest .
И перепишем эту команду в виде bake файла:
target "myapp" {
context = "."
dockerfile = "Dockerfile"
tags = ["myapp:latest"]
}
Теперь нам не нужно снова и снова писать длинную команду, достаточно один раз описать в файле и вызвать команду:
$ docker buildx bake myapp
Но это лишь примитивный пример, на деле же bake способен на многое о чем написано ниже в статье.
Анатомия Bake-файла
Рассмотрим основные компоненты bake файла:
1. Переменные (Variables)
Переменные позволяют определить значения, которые могут использоваться в разных местах конфигурации и легко переопределяться при запуске:
variable "TAG" {
default = "latest"
}
variable "DEBUG" {
default = "false"
}
Переменные можно использовать в других частях конфигурации через интерполяцию строк: ${TAG}
.
2. Группы (Groups)
Группы позволяют объединять несколько целей для одновременной сборки:
group "default" {
targets = ["app", "api"]
}
group "backend" {
targets = ["api", "database"]
}
3. Цели (Targets)
Цели — это основные единицы сборки, каждая из которых определяет один Docker-образ:
target "app" {
dockerfile = "Dockerfile.app"
context = "./app"
tags = ["myorg/app:${TAG}"]
args = {
DEBUG = "${DEBUG}"
}
platforms = ["linux/amd64", "linux/arm64"]
}
Основные параметры целей:
dockerfile
— путь к Dockerfilecontext
— контекст сборкиtags
— теги для образаargs
— аргументы для передачи в Dockerfileplatforms
— платформы для мультиплатформенной сборкиtarget
— цель многоступенчатой сборки в Dockerfileoutput
— куда выводить результат сборкиcache-from
иcache-to
— настройки кэширования
4. Наследование (Inheritance)
Одно из самых мощных свойств Bake — возможность наследования параметров:
target "base" {
context = "."
args = {
BASE_IMAGE = "node:16-alpine"
}
}
target "app" {
inherits = ["base"]
dockerfile = "app/Dockerfile"
tags = ["myapp/app:latest"]
}
Цель app
унаследует все параметры из base
и перезапишет или дополнит их своими.
5. Функции (Functions)
В HCL можно определять функции для более гибкой конфигурации:
function "tag" {
params = [name, version]
result = ["${name}:${version}"]
}
target "app" {
tags = tag("myapp/app", "v1.0.0")
}
Установка и настройка
Docker Bake является частью BuildKit — современного движка для сборки Docker-образов. Начиная с Docker 23.0, BuildKit включен по умолчанию, поэтому для большинства пользователей дополнительная настройка не требуется. Однако, если вы используете более старую версию Docker или хотите убедиться, что BuildKit активирован, следуйте инструкциям ниже.
Проверка версии Docker
Убедитесь, что у вас установлена актуальная версия Docker (23.0 или выше). Проверить версию можно командой:
docker --version
Если версия Docker устарела, обновите её, следуя официальной документации.
Активация BuildKit (для старых версий Docker)
Для версий Docker ниже 23.0 BuildKit необходимо активировать вручную. Это можно сделать одним из следующих способов:
Через переменную окружения:
export DOCKER_BUILDKIT=1
В конфигурационном файле Docker:
Отредактируйте файл~/.docker/config.json
и добавьте следующие параметры:{ "features": { "buildkit": true } }
Через командную строку:
При использовании команды docker build или docker buildx bake можно явно указать использование BuildKit:DOCKER_BUILDKIT=1 docker buildx bake
Установка Docker Buildx
Docker Buildx — это расширение Docker CLI, которое предоставляет дополнительные возможности для сборки образов, включая поддержку многоплатформенной сборки. Начиная с Docker 20.10, Buildx входит в состав Docker, но для полной функциональности рекомендуется убедиться, что он установлен и активирован.
Проверка установки Buildx:
docker buildx version
Если Buildx не установлен, следуйте инструкциям ниже(или официальной инструкции https://github.com/docker/buildx?tab=readme-ov-file#installing).
Установка Buildx:
Для Linux:
mkdir -p ~/.docker/cli-plugins curl -sSL https://github.com/docker/buildx/releases/latest/download/buildx-linux-amd64 -o ~/.docker/cli-plugins/docker-buildx chmod +x ~/.docker/cli-plugins/docker-buildx
Для macOS (с использованием Homebrew):
brew install docker-buildx
Создание и использование Buildx-билдера:
По умолчанию Docker использует встроенный билдер, но для полной функциональности рекомендуется создать новый билдер:docker buildx create --use --name my-builder
Проверьте, что билдер активен:
docker buildx ls
Основы использования Docker Bake
Конфигурационные файлы
Docker Bake использует конфигурационные файлы, которые могут быть написаны в форматах HCL (по умолчанию), JSON или YAML. Стандартные имена для этих файлов:
docker-bake.hcl
docker-bake.json
Также можно использовать docker-compose.yml
с некоторыми расширениями.
Структура HCL-файла
Типичный файл конфигурации Docker Bake имеет следующую структуру:
// Определение переменных
variable "TAG" {
default = "latest"
}
// Определение групп
group "default" {
targets = ["app", "api"]
}
// Определение общих настроек
target "docker-metadata-action" {
tags = ["user/app:${TAG}"]
}
// Определение целей сборки
target "app" {
inherits = ["docker-metadata-action"]
dockerfile = "Dockerfile.app"
context = "./app"
}
target "api" {
inherits = ["docker-metadata-action"]
dockerfile = "Dockerfile.api"
context = "./api"
}
Выполнение сборки
Собрать все цели из группы по умолчанию:
docker buildx bake
Собрать конкретную цель или группу:
docker buildx bake app
Передать переменные:
TAG=v1.0.0 docker buildx bake
Практические примеры
Пример 1: Простое многокомпонентное приложение
Предположим, у нас есть приложение, состоящее из веб-фронтенда, API и сервиса баз данных. Вот как может выглядеть файл docker-bake.hcl
:
variable "TAG" {
default = "latest"
}
group "default" {
targets = ["frontend", "api", "db"]
}
group "services" {
targets = ["api", "db"]
}
target "base" {
context = "."
args = {
BASE_IMAGE = "node:16-alpine"
}
}
target "frontend" {
inherits = ["base"]
dockerfile = "frontend/Dockerfile"
tags = ["myapp/frontend:${TAG}"]
args = {
API_URL = "http://api:3000"
}
}
target "api" {
inherits = ["base"]
dockerfile = "api/Dockerfile"
tags = ["myapp/api:${TAG}"]
args = {
DB_HOST = "db"
DB_PORT = "5432"
}
}
target "db" {
context = "./db"
dockerfile = "Dockerfile"
tags = ["myapp/db:${TAG}"]
}
Пример 2: Многоплатформенная сборка
Один из мощных аспектов Docker Bake — это простота настройки многоплатформенной сборки:
variable "TAG" {
default = "latest"
}
group "default" {
targets = ["app-all"]
}
target "app" {
dockerfile = "Dockerfile"
tags = ["myapp/app:${TAG}"]
}
target "app-linux-amd64" {
inherits = ["app"]
platforms = ["linux/amd64"]
}
target "app-linux-arm64" {
inherits = ["app"]
platforms = ["linux/arm64"]
}
target "app-all" {
inherits = ["app"]
platforms = ["linux/amd64", "linux/arm64"]
}
Пример 3: Разные среды разработки
Docker Bake позволяет легко управлять сборками для разных сред (например, разработки, тестирования и production). Для этого можно использовать переменные, которые переопределяются через командную строку:
variable "ENV" {
default = "dev"
}
group "default" {
targets = ["app-${ENV}"]
}
target "app-base" {
dockerfile = "Dockerfile"
args = {
BASE_IMAGE = "node:16-alpine"
}
}
target "app-dev" {
inherits = ["app-base"]
tags = ["myapp/app:dev"]
args = {
NODE_ENV = "development"
DEBUG = "true"
}
}
target "app-stage" {
inherits = ["app-base"]
tags = ["myapp/app:stage"]
args = {
NODE_ENV = "production"
API_URL = "https://api.stage.example.com"
}
}
target "app-prod" {
inherits = ["app-base"]
tags = ["myapp/app:prod", "myapp/app:latest"]
args = {
NODE_ENV = "production"
API_URL = "https://api.example.com"
}
}
Для сборки образа для конкретной среды используйте команду:
ENV=prod docker buildx bake
Продвинутые возможности Docker Bake
Матричные сборки
Docker Bake позволяет определять матрицы для создания нескольких вариантов сборки на основе комбинаций параметров:
variable "REGISTRY" {
default = "docker.io/myorg"
}
target "app" {
matrix = {
platform = ["linux/amd64", "linux/arm64"]
version = ["1.0", "2.0"]
}
name = "app-${version}-${platform}"
tags = ["${REGISTRY}/app:${version}-${platform}"]
platforms = [platform]
args = {
VERSION = version
}
}
Этот код создаст четыре варианта образа: для каждой комбинации платформы и версии. Вы можете собрать их все одной командой:
docker buildx bake app
Использование внешних файлов и функций
Docker Bake позволяет использовать внешние файлы и функции для более гибкой настройки:
// Импорт переменных из JSON-файла
variable "settings" {
default = {}
}
function "tag" {
params = [name, tag]
result = ["${name}:${tag}"]
}
target "app" {
dockerfile = "Dockerfile"
tags = tag("myapp/app", "v1.0.0")
args = {
CONFIG = "${settings.app_config}"
}
}
Затем можно передать файл с настройками:
docker buildx bake --file settings.json
Интеграция с Docker Compose
Docker Bake можно интегрировать с Docker Compose, что особенно удобно для существующих проектов:
# docker-compose.yml
services:
app:
build:
context: ./app
dockerfile: Dockerfile
args:
VERSION: "1.0"
image: myapp/app:latest
api:
build:
context: ./api
dockerfile: Dockerfile
image: myapp/api:latest
# docker-bake.hcl
target "default" {
context = "."
dockerfile-inline = <<EOT
FROM docker/compose:1.29.2
WORKDIR /app
COPY docker-compose.yml .
RUN docker-compose build
EOT
}
Условная логика
Для более сложных сценариев можно использовать условную логику:
variable "DEBUG" {
default = "false"
}
target "app" {
dockerfile = "Dockerfile"
tags = ["myapp/app:latest"]
args = {
DEBUG = "${DEBUG}"
EXTRA_PACKAGES = DEBUG == "true" ? "vim curl htop" : ""
}
}
Применение Docker Bake в CI/CD
Docker Bake отлично подходит для использования в CI/CD-пайплайнах. Вот пример интеграции с GitHub Actions, где используются секреты для безопасной аутентификации в Docker Hub:
# .github/workflows/build.yml
name: Build and Publish
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker Metadata
id: meta
uses: docker/metadata-action@v4
with:
images: myapp/app
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
- name: Build and push
uses: docker/bake-action@v2
with:
files: |
./docker-bake.hcl
targets: app
push: true
set: |
*.tags=${{ steps.meta.outputs.tags }}
Отладка и мониторинг сборок
Docker Bake предоставляет несколько полезных опций для отладки процесса сборки:
Просмотр конфигурации без сборки:
docker buildx bake --print
Подробные логи:
docker buildx bake --progress=plain
Экспорт в JSON для анализа:
docker buildx bake --print | jq
Сравнение с другими инструментами
Docker Bake vs. Docker Compose
Функция | Docker Bake | Docker Compose |
---|---|---|
Основное назначение | Сборка образов | Управление контейнерами |
Параллельная сборка | Да, автоматически | Ограничено |
Матричные сборки | Да | Нет |
Наследование | Да, мощная система | Ограничено (extends) |
Многоплатформенность | Да, интегрировано | Нет |
Формат конфигурации | HCL, JSON | YAML |
Docker Bake vs. Скрипты сборки
Аспект | Docker Bake | Bash/скрипты |
---|---|---|
Декларативность | Высокая | Низкая |
Сложность поддержки | Низкая | Высокая |
Переиспользование | Простое | Сложное |
Параллелизм | Автоматический | Ручной |
Интеграция с CI/CD | Простая | Требует усилий |
Лучшие практики
Организуйте таргеты в логические группы:
group "all" {
targets = ["app", "api", "worker"]
}
group "backend" {
targets = ["api", "worker"]
}
Используйте наследование для общих настроек:
target "common" {
context = "."
args = {
BASE_IMAGE = "node:16-alpine"
}
}
target "app" {
inherits = ["common"]
dockerfile = "app/Dockerfile"
}
Организуйте сложные конфигурации в несколько файлов:
docker buildx bake \
-f ./common.hcl \
-f ./development.hcl \
app-dev
Используйте переменные для гибкости:
variable "REGISTRY" {
default = "docker.io/myorg"
}
target "app" {
tags = ["${REGISTRY}/app:latest"]
}
Применяйте матрицы для сложных сценариев сборки:
target "matrix" {
matrix = {
env = ["dev", "prod"]
platform = ["linux/amd64", "linux/arm64"]
}
name = "app-${env}-${platform}"
tags = ["myapp/app:${env}-${platform}"]
}
Типичные проблемы и их решения
Проблема 1: Кэш не используется эффективно
Решение: Правильно структурируйте Dockerfile, размещая слои, которые меняются реже, в начале файла:
FROM node:16-alpine
# Сначала копируем только файлы для зависимостей
COPY package.json package-lock.json ./
RUN npm install
# Затем копируем исходный код
COPY . .
Проблема 2: Конфликт переменных окружения
Решение: Используйте явные значения в Docker Bake:
target "app" {
args = {
NODE_ENV = "production"
}
}
Проблема 3: Сложно отлаживать сборки
Решение: Используйте подробные логи и инспекцию:
docker buildx bake --progress=plain --print app
Docker Bake vs Docker Compose
Docker Bake упрощает и ускоряет процесс сборки, особенно если нужно собирать несколько образов одновременно. Вместо того, чтобы писать несколько команд docker build, всё можно описать в одном конфигурационном файле и запускать одной командой. Это также позволяет легко переопределять параметры (например, теги) и использовать параллельную сборку для ускорения процесса.
Docker Compose используется для управления контейнерами и их взаимосвязями (например, запуск нескольких сервисов в одном проекте).
Docker Bake используется для сборки Docker образов, причем он позволяет собирать несколько образов одновременно, ускоряя процесс.
Таким образом, docker-compose — для запуска контейнеров, а docker bake — для сборки образов.
Заключение
Docker Bake предоставляет мощный, гибкий и декларативный подход к организации сборки Docker-образов. Он решает многие проблемы, с которыми сталкиваются команды при использовании традиционных подходов к сборке, особенно в сложных многокомпонентных проектах.
Основные преимущества Docker Bake:
Декларативный подход
Эффективное использование кэша
Параллельная и многоплатформенная сборка
Мощная система переменных и наследования
Отличная интеграция с CI/CD-пайплайнами
Внедрение Docker Bake в рабочий процесс может значительно упростить и ускорить процессы сборки образов, особенно для команд, работающих с микросервисной архитектурой или сложными многокомпонентными приложениями.