Featured

Build docker image from multiple build contexts

Build docker image from multiple build contexts

Building a docker image requires specifying a source of truth to include in the image from a local directory or a remote git repository. In the previous version, the docker BuildKit allows users to specify the build context from a single source of truth only. However, the engineers may need to have the context from different locations based on the type of files. For instance, icons, images or other resources that are not included in the same package, including the resource from other docker images. Fortunately, the Docker Buildx toolkit supports multiple build context flag for Docker 1.4. Let's learn how to use this new feature.

The following list is a shortcut for jumping into a specific topic handy.

What version of Docker is this tutorial targeting?

All of the examples and use cases are working against Dockerfile 1.4 and Buildx v0.8+. Please ensure the version of Dockerfile and Buildx before starting this tutorial. If you would like to install or upgrade the Docker Engine, please refers to
Install Docker Engine page.

How to specify the version of Dockerfile frontend?

To specify the version of a Dockerfile frontend, by adding a line of code at the top of the file as:

# syntax=docker/dockerfile:1.4 ## List other instructions below this line

Learn More

How to install/upgrade Buildx?

It's very easy to install or upgrade Buildx tool by executing:

docker buildx install

Learn More

Why do we need the Build Context flag?

To build an image by docker, the single build context from a local repository is given from a path in the docker build command as:

docker build -t ${imageName}:${tag} .

However, this is not allowed to access files outside of specified build context by using parent selector(../) for security reasons. This leads to the build context must be at the root directory, sometimes it is not the project directory, if the developers would like to build an image for an application referencing multiple resources outside of the folder of an application. And it results in many COPY and ADD instructions being written in the Dockerfile to achieve this goal, which reduces the readability of code. Fortunately, Dockerfile 1.4 and Buildx v0.8+ support multiple build context flags right now. This reduces the complexity of Dockerfile and provides more flexibility in organising build contexts in the code with CI/CD pipeline.

What is the Build Context flag?

Build context flag allows developers to define the build contexts from local directory, Git repository, HTTP URL and Docker Registry.

The syntax of the build context flag is:

docker buildx build \ --build-context ${name}=${pathToContext} \ -t ${imageName}:${tag} \ .

${name} is the name of the build context, must be lowercase, which will be used in the Dockerfile and the ${pathToContext} is the location of the build context which could be local directory, remote git repository, HTTP URL to a tarball and Docker image.

Also, Dockerfile 1.4 supports multiple build contexts for an image by using:

docker build \ --build-context ${context1}=${pathToContext1} \ --build-context ${context2}=${pathToContext2} \ -t ${imageName}:${tag} \ .

By using build context flags, developers can copy the context outside of the project directory.

Load build context from a Local Directory

Load build context via relative file path

Build context flag accepts relative file path and absolute file path when the user would like to build an image from the files in a local directory.

The relative file path can be specified by the following two ways:

docker buildx build \ --build-context app=./workspaces/greeting \ -t ${imageName}:${tag} \ .

Learn More

Or

docker buildx build \ --build-context app=workspaces/greeting \ -t ${imageName}:${tag} \ .

Learn More

Then using COPY --from=${buildContextName} in the Dockerfile to copy files:

FROM node:14.17.1-alpine AS sourceStage WORKDIR /source COPY --from=app ["./", "/source/workspaces/greeting"]

Learn More

Combine multiple build contexts

Buildx can deal with multiple build contexts as well. For example, docker.tutorial is a monorepo that has a configuration file for packages at the root directory level as follows:

. +-- docker.tutorials | +-- dockerfiles | +-- greeting | +-- calculator | +-- workspaces | +-- greeting | +-- calculator | +-- package.json | +-- tsconfig.json | +-- README.md

It would be good to copy package.json file only through the build context flag rather than copying the whole directory to have the configuration file in the image.

docker buildx build \ --build-context repo=. \ --build-context app=workspaces/greeting \ -t ${IMG_NAME} \ .

Learn More

FROM node:14.17.1-alpine AS sourceStage WORKDIR /source COPY --from=repo ["./package.json", "/source"] COPY --from=repo ["./tsconfig.json", "/source"] COPY --from=app ["./", "/source/workspaces/greeting"]

Learn More

Load build context via absolute file path

If the build context is not in the same directory as follows:

. +-- resources | +-- icons +-- docker.tutorials | +-- dockerfiles | +-- greeting | +-- calculator | +-- workspaces | +-- greeting | +-- calculator | +-- package.json | +-- README.md

The absolute file path could be fit in this case:

docker buildx build \ --build-context resources=/Users/Documents/resources/icons \ -t ${imageName}:${tag} \ .

Learn More

Note: Please do not use tilde(~) in the file path. Buildx cannot find the build context in this form.

Alternatively, the build context can be accessed by notating double-dot(..) when the directory is in the parent folder of the current location:

docker buildx build \ --build-context resources=../resources/icons \ -t ${imageName} \

Learn More

Load build context from a Git repository

Clone the git repository via HTTPS

It is quite simple to load build context from a Git repository by specifying the URL of the repository and tagging the branch name with hash(#) at the end of the URL as:

docker buildx build \ --build-context repo=https://github.com/OttoHung/docker.tutorial.git#main \ -t ${imageName}:${tag} \ .

Learn More

By this way, the build context will be cloned to a directory whose name is as same as the repository in the WORKDIR. For example, the name of the repository is docker.tutorials then the build context will be stored in a directory named docker.tutorials as follows:

. +-- ${WORK_DIR} | +-- docker.tutorials | +-- README.md | +-- node_modules | +-- LICENSE +-- bin +-- usr

In the Dockerfile, using COPY --from=${buildContextName} to copy the build context from the temporary directory into a work directory as follows:

## Stage name is not compulsory if it is not a multi-stage build ## But `FROM` instruction must be provided. ## Learn more from https://docs.docker.com/engine/reference/builder/#from FROM node:14.17.1-alpine AS sourceStage WORKDIR /source COPY --from=repo ["./", "/source"]

Learn more

However, Buildx cannot clone a private repository when the access is unauthenticated. An error message is returned as fatal: could not read Username for 'https://github.com': terminal prompts disabled. [Learn More] It looks like Buildx clones the repository before executing instructions and there is no other way to provide access tokens to Buildx currently.

The solution for this circumstance could use --secret to pass Personal Access Token(PAT) to Buildx:

pat=${PAT} \ docker buildx build \ --secret id=pat \ -t ${IMG_NAME} \ .

Learn More

Then using RUN instruction to clone the remote repository which is much safer than embedded the PAT in the URL in the command line.

RUN --mount=type=secret,id=${SECRET_ID} \ PAT=$(cat /run/secrets/${SECRET_ID}) \ git clone https://${PAT}@${REPO_URL}.git

Learn More

Clone the git repository via SSH

By assigning a SSH URL to Buildx like:

docker buildx build \ --build-context repo=git@github.com:OttoHung/docker.tutorials.git#main \ -t ${imageName}:${tag} \ .

Learn More

However, the remote repository cannot be cloned and an error message returned as unsupported context source **git@github.com** for ${BUILD_CONTEXT_NAME}.

To resolve this issue, it is recommended to use --ssh and RUN instruction with Buildx to clone the repository as follows:

docker buildx build \ --secret id=npm,src=$HOME/.npmrc \ --ssh default=$SSH_AUTH_SOCK \ -t ${IMG_NAME} \ .

Learn More

FROM node:14.17.1-alpine AS sourceStage WORKDIR /source ## node-alpine doesn't include openssh-client and git by default RUN apk update && apk upgrade \ && apk add openssh-client git ## To store SSH key temporarily RUN mkdir -p -m 0700 ~/.ssh \ && ssh-keyscan -H ${HOSTING} >> ~/.ssh/known_hosts RUN --mount=type=ssh git clone ${REPO}

Learn More

Then the build context will be cloned into the same folder structure listed at Clone the git repository via HTTPS

Load build context from a tarball via HTTP URL

Build context flag also supports downloading tarball(*.tar or *.tar.gz) via an HTTP URL as:

docker buildx build \ --build-context dockerTutorial=https://github.com/OttoHung/docker.tutorials/archive/refs/tags/tarball.tar.gz \ -t ${imageName}:${tag} \ .

Learn More

Once the tarball file has been downloaded, it is required to unpack tarball before copying the contents to another work directory. On macOS, tar -xzf could be used to unpack the tarball.

FROM node:14.17.1-alpine as sourceStage WORKDIR /source ARG TARBALL_NAME ## tarball must be downloaded by COPY instruction then be unarchived ## by tar command COPY --from=tarball ["./", "/source"] RUN tar -xzf ${TARBALL_NAME}

Learn More

It is recommended to double-check the directory name is as same as the tarball name or not before making a tarball. If the names of both are different, such as the tarball packed by GitHub Release, the folder name must be given to the Dockerfile to copy the contents because the tarball name is not the same.

Load build context from a docker image

Dockerfile 1.4 supports that the docker image can be loaded by FROM instruction with the URL of the image as:

FROM https://ghcr.io/OttoHung/greeting:latest

Or specify the name of the docker image:

FROM alpine:3.15

Learn More

In the command prompt, the docker image must go with docker-image:// prefix to tell what docker image to load. For example:

docker buildx build --build-context image=docker-image://alpine:3.15 \ -t ${imageName}:${tag} \ .

Learn More

Then using FROM instruction to import the image:

FROM image AS sourceStage

Learn More

Also, an image digest following up on the tag of the image can be used to specify a particular version for the image as:

docker buildx build --build-context image=docker-image://ghcr.io/ottohung/docker.tutorials:git-https@sha256:b9f33235036f68d3d510a8800e27e3b6514142119cbd5f4862f0506d8cc8552d \ -t ${imageName}:${tag} \ .

Learn More

Build Context flag is also a convenient way to specify other docker images for the build, such as base image and image for app, from command prompt dynamically. In this way, it's easy to change the base images without modifying Dockerfile and it's not necessary to write multiple Dockerfile for different platforms.

docker buildx build --build-context baseImage=docker-image://alpine:3.15 \ --build-context appImage=docker-image://node:14.17.1-alpine \

Reference

Comments

Popular Posts