How to use docker buildx build command to build and manage images

DockerDockerBeginner
Practice Now

Introduction

In this lab, you will gain practical experience using the docker buildx build command to build and manage Docker images. You will start by building a simple image with default settings, learning how to define image instructions using a Dockerfile.

Moving beyond the basics, you will explore more advanced features such as utilizing build arguments and targeting specific stages within a multi-stage build. You will also learn how to effectively manage build cache to optimize build times using --cache-from and --cache-to. Furthermore, the lab will guide you through building multi-platform images and pushing them to a registry, and demonstrate how to securely expose secrets and SSH agents during the build process.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL docker(("Docker")) -.-> docker/ContainerOperationsGroup(["Container Operations"]) docker(("Docker")) -.-> docker/ImageOperationsGroup(["Image Operations"]) docker(("Docker")) -.-> docker/SystemManagementGroup(["System Management"]) docker(("Docker")) -.-> docker/DockerfileGroup(["Dockerfile"]) docker/ContainerOperationsGroup -.-> docker/run("Run a Container") docker/ImageOperationsGroup -.-> docker/push("Push Image to Repository") docker/ImageOperationsGroup -.-> docker/rmi("Remove Image") docker/ImageOperationsGroup -.-> docker/images("List Images") docker/SystemManagementGroup -.-> docker/login("Log into Docker Registry") docker/DockerfileGroup -.-> docker/build("Build Image from Dockerfile") subgraph Lab Skills docker/run -.-> lab-555045{{"How to use docker buildx build command to build and manage images"}} docker/push -.-> lab-555045{{"How to use docker buildx build command to build and manage images"}} docker/rmi -.-> lab-555045{{"How to use docker buildx build command to build and manage images"}} docker/images -.-> lab-555045{{"How to use docker buildx build command to build and manage images"}} docker/login -.-> lab-555045{{"How to use docker buildx build command to build and manage images"}} docker/build -.-> lab-555045{{"How to use docker buildx build command to build and manage images"}} end

Build a simple image with default settings

In this step, you will learn how to build a simple Docker image using a Dockerfile. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Docker can build images automatically by reading the instructions from a Dockerfile.

First, navigate to the ~/project directory, which is your working directory for this lab.

cd ~/project

Now, let's create a simple Dockerfile. This Dockerfile will define an image based on the ubuntu base image and will simply print "Hello, Docker!" when a container is run from this image.

Use the nano editor to create a file named Dockerfile in the ~/project directory.

nano Dockerfile

Add the following content to the Dockerfile:

FROM ubuntu:latest
CMD ["echo", "Hello, Docker!"]

Let's break down this simple Dockerfile:

  • FROM ubuntu:latest: This instruction sets the base image for our new image. We are using the latest version of the official Ubuntu image from Docker Hub.
  • CMD ["echo", "Hello, Docker!"]: This instruction specifies the command that will be executed when a container is started from this image. In this case, it will run the echo command with the argument "Hello, Docker!".

Save the file by pressing Ctrl + S and exit the nano editor by pressing Ctrl + X.

Now that we have our Dockerfile, we can build the image using the docker build command. The . at the end of the command tells Docker to look for the Dockerfile in the current directory (~/project). We will also tag the image with a name, for example, my-hello-image.

docker build -t my-hello-image .

You will see output indicating that Docker is building the image layer by layer. It will first pull the ubuntu:latest image if it's not already present on your system, and then execute the CMD instruction.

After the build is complete, you can verify that the image was created successfully by listing the available images using the docker images command.

docker images

You should see my-hello-image listed in the output.

Finally, let's run a container from our newly built image to see the output of the CMD instruction.

docker run my-hello-image

You should see the output "Hello, Docker!" printed to your terminal. This confirms that our image was built correctly and the CMD instruction is working as expected.

Use build arguments and target stages

In this step, you will learn how to use build arguments (ARG) and target stages in your Dockerfile to create more flexible and efficient builds. Build arguments allow you to pass variables to the build process, while target stages enable you to define multiple build stages within a single Dockerfile and build only a specific stage.

First, make sure you are in the ~/project directory.

cd ~/project

Let's modify our existing Dockerfile to include a build argument and a simple multi-stage build. We will define an argument for a greeting message and use a second stage to copy a file from the first stage.

Open the Dockerfile using nano:

nano Dockerfile

Replace the existing content with the following:

## Stage 1: Builder stage
FROM ubuntu:latest as builder
ARG GREETING="Hello from build argument!"
RUN echo $GREETING > /app/greeting.txt

## Stage 2: Final stage
FROM ubuntu:latest
COPY --from=builder /app/greeting.txt /greeting.txt
CMD ["cat", "/greeting.txt"]

Let's understand the changes:

  • ARG GREETING="Hello from build argument!": This instruction defines a build argument named GREETING with a default value. You can override this default value during the build process.
  • FROM ubuntu:latest as builder: This starts the first build stage and names it builder. This is useful for multi-stage builds.
  • RUN echo $GREETING > /app/greeting.txt: In the builder stage, this command creates a file named greeting.txt in the /app directory and writes the value of the GREETING argument into it.
  • FROM ubuntu:latest: This starts the second build stage. By default, this will be the final image.
  • COPY --from=builder /app/greeting.txt /greeting.txt: This instruction copies the greeting.txt file from the builder stage (specifically from /app/greeting.txt) to the root directory (/) of the current stage. This is how you transfer artifacts between stages in a multi-stage build.
  • CMD ["cat", "/greeting.txt"]: This sets the command to be executed when a container from the final image is run. It will print the content of the /greeting.txt file.

Save the Dockerfile and exit nano.

Now, let's build the image using the docker build command. We will use the --build-arg flag to pass a custom value for the GREETING argument. We will also tag this image as my-arg-image.

docker build --build-arg GREETING="Greetings from the command line!" -t my-arg-image .

Observe the output. You should see the build process using the provided build argument.

After the build is complete, list the images to confirm my-arg-image is present.

docker images

Now, run a container from my-arg-image to see the output.

docker run my-arg-image

You should see "Greetings from the command line!" printed, confirming that the build argument was used.

Next, let's build only the builder stage. This is useful for creating intermediate images that contain build artifacts without the final application. We use the --target flag to specify the stage name.

docker build --target builder -t my-builder-stage .

List the images again. You should now see my-builder-stage in addition to my-arg-image.

docker images

Running a container from my-builder-stage won't produce the same output as my-arg-image because the CMD instruction from the final stage is not included in the builder stage. Let's try to run it and see what happens (it will likely just start and exit quickly as there's no default command).

docker run my-builder-stage

This demonstrates how target stages allow you to control which part of your Dockerfile is built into an image.

Manage build cache with --cache-from and --cache-to

In this step, you will learn how to manage the Docker build cache using the --cache-from and --cache-to flags. The build cache can significantly speed up subsequent builds by reusing layers from previous builds. --cache-from allows you to specify an image to use as a cache source, and --cache-to allows you to export the build cache to a specified location (like a registry or a local directory).

First, ensure you are in the ~/project directory.

cd ~/project

Let's modify our Dockerfile slightly to simulate a change that would normally break the cache. We'll add a simple RUN instruction.

Open the Dockerfile with nano:

nano Dockerfile

Add the following line after the ARG instruction in the builder stage:

RUN echo "Adding a new layer"

The updated Dockerfile should look like this:

## Stage 1: Builder stage
FROM ubuntu:latest as builder
ARG GREETING="Hello from build argument!"
RUN echo "Adding a new layer"
RUN echo $GREETING > /app/greeting.txt

## Stage 2: Final stage
FROM ubuntu:latest
COPY --from=builder /app/greeting.txt /greeting.txt
CMD ["cat", "/greeting.txt"]

Save the Dockerfile and exit nano.

Now, let's build the image again without using any specific cache options. Docker will automatically use the local cache if available.

docker build -t my-cached-image .

You should see that some layers are built from scratch because we added a new instruction, invalidating the cache for subsequent instructions.

Now, let's simulate a scenario where you might want to use a previously built image as a cache source, perhaps from a registry or another build. For demonstration purposes, we will use the my-cached-image we just built as the cache source for a new build.

First, let's make a small change to the Dockerfile again to simulate another modification.

Open the Dockerfile:

nano Dockerfile

Change the message in the new RUN instruction:

RUN echo "Adding another new layer"

The updated Dockerfile should look like this:

## Stage 1: Builder stage
FROM ubuntu:latest as builder
ARG GREETING="Hello from build argument!"
RUN echo "Adding another new layer"
RUN echo $GREETING > /app/greeting.txt

## Stage 2: Final stage
FROM ubuntu:latest
COPY --from=builder /app/greeting.txt /greeting.txt
CMD ["cat", "/greeting.txt"]

Save and exit nano.

Now, build the image again, but this time use the --cache-from flag to specify my-cached-image as the cache source.

docker build --cache-from my-cached-image -t my-cached-image-from .

You should observe that Docker attempts to use layers from my-cached-image. The layer corresponding to the first RUN instruction will likely be rebuilt because the instruction changed, but subsequent layers might be pulled from the cache if they match.

The --cache-to flag is used to export the build cache. This is particularly useful in CI/CD pipelines to share cache between builds. To use --cache-to, you typically need to use a build driver that supports cache export, such as the docker-container driver.

First, let's install Docker Compose, which is often used with buildx.

sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

Now, let's create a buildx builder instance.

docker buildx create --use

Now, let's build the image and export the cache to a local directory. We'll use the local cache exporter.

docker buildx build --cache-to type=local,dest=./build-cache -t my-exported-cache-image . --load
  • docker buildx build: Uses the buildx tool for building.
  • --cache-to type=local,dest=./build-cache: Exports the cache to a local directory named build-cache in the current directory.
  • -t my-exported-cache-image: Tags the resulting image.
  • .: Specifies the build context (current directory).
  • --load: Loads the built image into the local Docker image cache.

You should see output indicating the build and the cache export. A build-cache directory will be created in your ~/project directory.

Now, let's simulate a clean build environment and try to build using the exported cache. First, let's remove the images we built.

docker rmi my-cached-image my-cached-image-from my-exported-cache-image

Now, build the image again, this time using the exported cache as the source.

docker buildx build --cache-from type=local,src=./build-cache -t my-imported-cache-image . --load
  • --cache-from type=local,src=./build-cache: Imports the cache from the local directory build-cache.

You should observe that Docker uses the layers from the exported cache, significantly speeding up the build process compared to building from scratch.

Build multi-platform images and push to a registry

In this step, you will learn how to build Docker images for multiple architectures (like linux/amd64 and linux/arm64) and push them to a container registry. Building multi-platform images is essential for ensuring your applications can run on different types of hardware. We will use Docker Buildx, which you initialized in the previous step.

First, ensure you are in the ~/project directory.

cd ~/project

We will use our existing Dockerfile for this step. Let's build an image for both linux/amd64 and linux/arm64 platforms. We will tag the image with a name and a version, for example, your-dockerhub-username/my-multi-platform-image:latest. Replace your-dockerhub-username with your actual Docker Hub username. If you don't have a Docker Hub account, you can create one for free.

To build for multiple platforms, we use the --platform flag with docker buildx build. We also need to use the --push flag to push the resulting manifest list and images to a registry.

docker buildx build --platform linux/amd64,linux/arm64 -t your-dockerhub-username/my-multi-platform-image:latest . --push

Note: This command will require you to log in to Docker Hub if you haven't already. You can log in using the docker login command in a separate terminal session if needed.

docker login

Enter your Docker Hub username and password when prompted.

After logging in, run the docker buildx build command again.

The build process will take longer than a single-platform build as it builds the image for each specified architecture. After the build is complete, Buildx will create a manifest list that references the images for each platform and push everything to your specified Docker Hub repository.

You can verify that the multi-platform image was pushed by visiting your Docker Hub repository in a web browser. You should see the my-multi-platform-image with the latest tag, and under the "Tags" tab, you should see that it supports multiple architectures.

Alternatively, you can use the docker buildx imagetools inspect command to inspect the manifest list you just pushed. Replace your-dockerhub-username with your username.

docker buildx imagetools inspect your-dockerhub-username/my-multi-platform-image:latest

The output will show the manifest list and the different images (with their respective architectures) that it points to.

This demonstrates how to build and push images that can run on different CPU architectures, making your Docker images more versatile.

Expose secrets and SSH agent to the build

In this step, you will learn how to securely expose secrets and your SSH agent to the Docker build process using Buildx. This is crucial for scenarios where your build needs to access private repositories, install private packages, or interact with external services that require authentication, without embedding sensitive information directly in your Dockerfile.

First, ensure you are in the ~/project directory.

cd ~/project

We will modify our Dockerfile to demonstrate how to use a secret during the build. For this example, we'll create a dummy secret file and read its content during the build.

Create a file named mysecret.txt in the ~/project directory with some secret content.

echo "This is a secret message!" > ~/project/mysecret.txt

Now, open the Dockerfile with nano:

nano Dockerfile

Add a new RUN instruction in the builder stage that uses the --mount=type=secret flag to access the secret.

## Stage 1: Builder stage
FROM ubuntu:latest as builder
ARG GREETING="Hello from build argument!"
RUN echo "Adding another new layer"
RUN echo $GREETING > /app/greeting.txt
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret > /app/secret_content.txt

## Stage 2: Final stage
FROM ubuntu:latest
COPY --from=builder /app/greeting.txt /greeting.txt
COPY --from=builder /app/secret_content.txt /secret_content.txt
CMD ["cat", "/greeting.txt", "/secret_content.txt"]

Let's understand the new instruction:

  • RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret > /app/secret_content.txt: This instruction mounts a secret into the build container.
    • --mount=type=secret: Specifies that we are mounting a secret.
    • id=mysecret: This is the ID of the secret that we will provide during the build.
    • cat /run/secrets/mysecret: Inside the build container, the secret is available at /run/secrets/mysecret. We are using cat to read its content.
    • > /app/secret_content.txt: We redirect the secret content to a file named secret_content.txt in the /app directory within the builder stage.

We also added a COPY instruction in the final stage to copy the secret_content.txt file from the builder stage.

Save the Dockerfile and exit nano.

Now, build the image using docker buildx build and provide the secret using the --secret flag.

docker buildx build --secret id=mysecret,src=~/project/mysecret.txt -t my-secret-image . --load
  • --secret id=mysecret,src=~/project/mysecret.txt: This flag provides the secret to the build.
    • id=mysecret: Matches the ID specified in the Dockerfile.
    • src=~/project/mysecret.txt: Specifies the path to the secret file on your local machine.

The build process will now have access to the content of mysecret.txt during the execution of the RUN --mount=type=secret... instruction. The secret content is not stored in the final image layers.

After the build is complete, run a container from the image.

docker run my-secret-image

You should see both the greeting message and the content of your secret file printed to the console.

Now, let's demonstrate exposing your SSH agent to the build. This is useful for cloning private Git repositories during the build process.

First, ensure your SSH agent is running and has your key loaded. You can typically check this with ssh-add -l. If your agent is not running or your key is not added, you may need to start the agent and add your key.

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa ## Replace with your SSH key path if different

Now, modify the Dockerfile to use the SSH agent. We'll add a RUN instruction that attempts to clone a (non-existent) private repository using SSH.

Open the Dockerfile:

nano Dockerfile

Add a new RUN instruction in the builder stage that uses the --mount=type=ssh flag.

## Stage 1: Builder stage
FROM ubuntu:latest as builder
ARG GREETING="Hello from build argument!"
RUN echo "Adding another new layer"
RUN echo $GREETING > /app/greeting.txt
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret > /app/secret_content.txt
RUN --mount=type=ssh git clone [email protected]:your-username/your-private-repo.git || echo "Skipping git clone as this is a demo"

## Stage 2: Final stage
FROM ubuntu:latest
COPY --from=builder /app/greeting.txt /greeting.txt
COPY --from=builder /app/secret_content.txt /secret_content.txt
CMD ["cat", "/greeting.txt", "/secret_content.txt"]

Note: Replace your-username/your-private-repo.git with a placeholder for a private repository URL. The || echo "Skipping git clone as this is a demo" part is added so the build doesn't fail if the repository doesn't exist or you don't have access.

Save the Dockerfile and exit nano.

Now, build the image using docker buildx build and provide access to your SSH agent using the --ssh flag.

docker buildx build --ssh default -t my-ssh-image . --load
  • --ssh default: This flag exposes your default SSH agent to the build.

During the build, the RUN --mount=type=ssh... instruction will be able to use your SSH agent to authenticate with the Git server.

After the build is complete, you can run the image, although the output will be the same as before since the git clone operation was likely skipped.

docker run my-ssh-image

This demonstrates how to securely use secrets and your SSH agent during the Docker build process with Buildx, preventing sensitive information from being embedded in your images.

Summary

In this lab, you learned the fundamentals of building Docker images using a Dockerfile. You started by creating a simple Dockerfile that defines an image based on Ubuntu and executes a basic command. You then used the docker build command to build this image, understanding how Docker processes the instructions in the Dockerfile layer by layer and how to tag the resulting image.

Building upon the basics, you explored more advanced features of the docker buildx build command. This included utilizing build arguments to pass dynamic values into the build process and leveraging target stages within a multi-stage build to optimize image size and build time. You also learned how to manage the build cache effectively using --cache-from and --cache-to to speed up subsequent builds. Furthermore, you gained experience in building multi-platform images, enabling your images to run on different architectures, and pushing these multi-platform images to a container registry. Finally, you discovered how to securely expose secrets and utilize an SSH agent during the build process, enhancing the security and flexibility of your image builds.