DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • A Guide for Deploying .NET 10 Applications Using Docker's New Workflow
  • A Beginner's Guide to Docker Compose
  • A Beginner's Guide to Essential Commands to Fix Container Setup Issues
  • Expert Techniques to Trim Your Docker Images and Speed Up Build Times

Trending

  • Feature Flag Debt: Performance Impact in Enterprise Applications
  • When Snowflake Lies to You: Understanding False Failures in dbt Pipelines
  • Multi-Scale Feature Learning in CNN and U-Net Architectures
  • Compliance Automated Standard Solution (COMPASS), Part 10: How OSCAL Mapping Paves the Way for Continuous Compliance Scalability
  1. DZone
  2. Software Design and Architecture
  3. Containers
  4. Docker Bake: A Modern Approach to Container Building

Docker Bake: A Modern Approach to Container Building

Docker Bake offers a powerful, declarative approach to building container images with parallel processing, efficient caching, and inheritance.

By 
Suleiman Dibirov user avatar
Suleiman Dibirov
DZone Core CORE ·
Mar. 03, 25 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
12.0K Views

Join the DZone community and get the full member experience.

Join For Free

The traditional way of building Docker images using the docker build command is simple and straightforward, but when working with complex applications consisting of multiple components, this process can become tedious and error-prone. This is where Docker Bake comes in — a powerful and flexible tool for organizing multi-stage and parallel image building.

In this article, we'll look at the capabilities of Docker Bake, its advantages over the standard approach, and practical examples of its use for various development scenarios.

What Is Docker Bake?

Docker Bake is a BuildKit feature that allows you to organize and automate the Docker image-building process using configuration files.

The main advantages of Docker Bake:

  • Declarative syntax. Instead of multiple commands in scripts, you describe the desired result in HCL (HashiCorp Configuration Language), JSON, or YAML (Docker Compose files).
  • Parallel building. BuildKit automatically performs image building in parallel where possible.
  • Cache reuse. Efficient use of cache between different builds.
  • Grouping and targeted builds. Ability to define groups of images and build only the targets needed at the moment.
  • Variables and inheritance. A powerful system of variables and property inheritance between build targets.
  • CI/CD integration. Easily integrates into continuous integration and delivery pipelines.

Anatomy of a Bake File

Let's look at the main components of a bake file:

1. Variables

Variables allow you to define values that can be used in different parts of the configuration and easily redefined at runtime:

Shell
 
variable "TAG" {
  default = "latest"
}

variable "DEBUG" {
  default = "false"
}


Variables can be used in other parts of the configuration through string interpolation: ${TAG}.

2. Groups

Groups allow you to combine multiple targets for simultaneous building:

Shell
 
group "default" {
  targets = ["app", "api"]
}

group "backend" {
  targets = ["api", "database"]
}


3. Targets

Targets are the main units of building, each defining one Docker image:

Shell
 
target "app" {
  dockerfile = "Dockerfile.app"
  context = "./app"
  tags = ["myorg/app:${TAG}"]
  args = {
    DEBUG = "${DEBUG}"
  }
  platforms = ["linux/amd64", "linux/arm64"]
}


Main target parameters:

  • dockerfile – path to the Dockerfile
  • context – build context
  • tags – tags for the image
  • args – arguments to pass to the Dockerfile
  • platforms – platforms for multi-platform building
  • target – target for multi-stage building in Dockerfile
  • output – where to output the build result
  • cache-from and cache-to – cache settings

4. Inheritance

One of the most powerful features of Bake is the ability to inherit parameters:

Shell
 
target "base" {
  context = "."
  args = {
    BASE_IMAGE = "node:16-alpine"
  }
}

target "app" {
  inherits = ["base"]
  dockerfile = "app/Dockerfile"
  tags = ["myapp/app:latest"]
}


The app target will inherit all parameters from the base and overwrite or supplement them with its own.

5. Functions

In HCL, you can define functions for more flexible configuration:

Shell
 
function "tag" {
  params = [name, version]
  result = ["${name}:${version}"]
}

target "app" {
  tags = tag("myapp/app", "v1.0.0")
}


Installation and Setup

Docker Bake is part of BuildKit, a modern engine for building Docker images. Starting with Docker 23.0, BuildKit is enabled by default, so most users don't need additional configuration. However, if you're using an older version of Docker or want to make sure BuildKit is activated, follow the instructions below.

Checking the Docker Version

Make sure you have an up-to-date version of Docker (23.0 or higher). You can check the version with the command:

Plain Text
 
docker --version


If your Docker version is outdated, update it following the official documentation.

Activating BuildKit (for old Docker versions)

For Docker versions below 23.0, BuildKit needs to be activated manually. This can be done in one of the following ways:

  • Via environment variable:

    Shell
     
    export DOCKER_BUILDKIT=1
    Plain Text
     
    In the Docker configuration file: Edit the ~/.docker/config.json file and add the following parameters:


  • JSON
     
    {
      "features": {
        "buildkit": true
      }
    }


  • Via command line: When using the docker build or docker buildx bake command, you can explicitly specify the use of BuildKit:

    Shell
     
    DOCKER_BUILDKIT=1 docker buildx bake


Installing Docker Buildx

Docker Buildx is an extension of the Docker CLI that provides additional capabilities for building images, including support for multi-platform building. Starting with Docker 20.10, Buildx is included with Docker, but for full functionality, it's recommended to ensure it's installed and activated.

Check Buildx Installation

Shell
 
docker buildx version


If Buildx is not installed, follow the instructions below.

Installing Buildx

  • For Linux:
    Shell
     
    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
  • For macOS (using Homebrew):
    Shell
     
    brew install docker-buildx

Creating and Using a Buildx Builder

By default, Docker uses the built-in builder, but for full functionality, it's recommended to create a new builder:

Shell
 
docker buildx create --use --name my-builder


Check that the builder is active:

Shell
 
docker buildx ls


Docker Bake Basics

Configuration Files

Docker Bake uses configuration files that can be written in HCL (default), JSON, or YAML formats. Standard names for these files:

  • docker-bake.hcl
  • docker-bake.json

You can also use docker-compose.yml with some extensions.

HCL File Structure

A typical Docker Bake configuration file has the following structure:

Shell
 
// Defining variables
variable "TAG" {
  default = "latest"
}

// Defining groups
group "default" {
  targets = ["app", "api"]
}

// Defining common settings
target "docker-metadata-action" {
  tags = ["user/app:${TAG}"]
}

// Defining build targets
target "app" {
  inherits = ["docker-metadata-action"]
  dockerfile = "Dockerfile.app"
  context = "./app"
}

target "api" {
  inherits = ["docker-metadata-action"]
  dockerfile = "Dockerfile.api"
  context = "./api"
}


Executing the Build

Build all targets from the default group:

Plain Text
 
docker buildx bake


Build a specific target or group:

Plain Text
 
docker buildx bake app


Pass variables:

Plain Text
 
TAG=v1.0.0 docker buildx bake


Practical Examples

Example 1: Simple Multi-Component Application

Suppose we have an application consisting of a web frontend, API, and database service. Here's what a docker-bake.hcl file might look like:

Shell
 
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}"]
}


Example 2: Multi-Platform Building

One of the powerful aspects of Docker Bake is the ease of setting up multi-platform building:

Shell
 
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"]
}


Example 3: Different Development Environments

Docker Bake makes it easy to manage builds for different environments (e.g., development, testing, and production). For this, you can use variables that are overridden via the command line:

Shell
 
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"
  }
}


To build an image for a specific environment, use the command:

Plain Text
 
ENV=prod docker buildx bake


Advanced Docker Bake Features

Matrix Builds

Docker Bake allows you to define matrices for creating multiple build variants based on parameter combinations:

Shell
 
variable "REGISTRY" {
  default = "docker.io/myorg"
}

target "matrix" {
  name = "app-${platform}-${version}"
  matrix = {
    platform = ["linux/amd64", "linux/arm64"]
    version = ["1.0", "2.0"]
  }
  dockerfile = "Dockerfile"
  tags = ["${REGISTRY}/app:${version}-${platform}"]
  platforms = ["${platform}"]
  args = {
    VERSION = "${version}"
  }
}


This code will create four image variants for each combination of platform and version. You can build them all with a single command.

Using External Files and Functions

Docker Bake allows you to use external files and functions for more flexible configuration:

Shell
 
// Import variables from a JSON file
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}"
  }
}


Then you can pass a settings file:

Plain Text
 
docker buildx bake --file settings.json


Integration With Docker Compose

Docker Bake can be integrated with Docker Compose, which is especially convenient for existing projects:

YAML
 
# 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
Shell
 
# 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
}


Conditional Logic

For more complex scenarios, you can use conditional logic:

Shell
 
variable "DEBUG" {
  default = "false"
}

target "app" {
  dockerfile = "Dockerfile"
  tags = ["myapp/app:latest"]
  args = {
    DEBUG = "${DEBUG}"
    EXTRA_PACKAGES = DEBUG == "true" ? "vim curl htop" : ""
  }
}


Using Docker Bake in CI/CD

Docker Bake is perfect for use in CI/CD pipelines. Here's an example of integration with GitHub Actions, using secrets for secure authentication in Docker Hub:

Shell
 
# .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 }}


Debugging and Monitoring Builds

Docker Bake provides several useful options for debugging the build process:

View configuration without building:

Plain Text
 
docker buildx bake --print


Detailed logs:

Plain Text
 
docker buildx bake --progress=plain


Export to JSON for analysis:

Plain Text
 
docker buildx bake --print | jq


Comparison With Other Tools

Docker Bake vs. Docker Compose

Feature Docker Bake Docker Compose
Main purpose Building images Container management
Parallel building Yes, automatically Limited
Matrix builds Yes No
Inheritance Yes, powerful system Limited (extends)
Multi-platform Yes, integrated No
Configuration format HCL, JSON YAML


Docker Bake vs. Build Scripts

Aspect Docker Bake Bash/scripts
Declarativeness High Low
Maintenance complexity Low High
Reusability Simple Complex
Parallelism Automatic Manual
CI/CD integration Simple Requires effort


Best Practices

Organize targets into logical groups:

Shell
 
group "all" {
  targets = ["app", "api", "worker"]
}

group "backend" {
  targets = ["api", "worker"]
}


Use inheritance for common settings:

Shell
 
target "common" {
  context = "."
  args = {
    BASE_IMAGE = "node:16-alpine"
  }
}

target "app" {
  inherits = ["common"]
  dockerfile = "app/Dockerfile"
}


Organize complex configurations into multiple files:

Shell
 
docker buildx bake \
  -f ./common.hcl \
  -f ./development.hcl \
  app-dev


Use variables for flexibility:

Shell
 
variable "REGISTRY" {
  default = "docker.io/myorg"
}

target "app" {
  tags = ["${REGISTRY}/app:latest"]
}


Apply matrices for complex build scenarios:

Shell
 
target "matrix" {
  matrix = {
    env = ["dev", "prod"]
    platform = ["linux/amd64", "linux/arm64"]
  }
  name = "app-${env}-${platform}"
  tags = ["myapp/app:${env}-${platform}"]
}


Common Problems and Solutions

Problem 1: Cache is Not Used Efficiently

Solution

Properly structure your Dockerfile, placing layers that change less frequently at the beginning of the file:

Dockerfile
 
FROM node:16-alpine

# First copy only dependency files
COPY package.json package-lock.json ./
RUN npm install

# Then copy the source code
COPY . .


Problem 2: Environment variable conflicts

Solution

Use explicit values in Docker Bake:

Shell
 
target "app" {
  args = {
    NODE_ENV = "production"
  }
}


Problem 3: Difficult to debug builds

Solution

Use detailed logs and inspection:

Plain Text
 
docker buildx bake --progress=plain --print app


Conclusion

Docker Bake provides a powerful, flexible, and declarative approach to organizing Docker image building. It solves many problems that teams face when using traditional build approaches, especially in complex multi-component projects.

The main advantages of Docker Bake:

  • Declarative approach
  • Efficient cache usage
  • Parallel and multi-platform building
  • Powerful variable and inheritance system
  • Excellent integration with CI/CD pipelines

Implementing Docker Bake in your workflow can significantly simplify and speed up image-building processes, especially for teams working with microservice architecture or complex multi-component applications.

Useful Resources

  • Official Docker Bake documentation
  • BuildKit GitHub repository
  • HCL documentation
  • Docker Buildx GitHub repository
app Docker (software) Container

Opinions expressed by DZone contributors are their own.

Related

  • A Guide for Deploying .NET 10 Applications Using Docker's New Workflow
  • A Beginner's Guide to Docker Compose
  • A Beginner's Guide to Essential Commands to Fix Container Setup Issues
  • Expert Techniques to Trim Your Docker Images and Speed Up Build Times

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook