diff --git a/build/building/multi-platform.md b/build/building/multi-platform.md index 03b5a5e8d9..a1cbda7357 100644 --- a/build/building/multi-platform.md +++ b/build/building/multi-platform.md @@ -1,6 +1,6 @@ --- title: Multi-platform images -description: Different strategies for building multi-platform images +description: Introduction to multi-platform images and how to build them keywords: build, buildx, buildkit, multi-platform images redirect_from: - /build/buildx/multiplatform-images/ @@ -13,8 +13,8 @@ Docker images can support multiple platforms, which means that a single image may contain variants for different architectures, and sometimes for different operating systems, such as Windows. -When running an image with multi-platform support, `docker` automatically -selects the image that matches your OS and architecture. +When you run an image with multi-platform support, Docker automatically selects +the image that matches your OS and architecture. Most of the Docker Official Images on Docker Hub provide a [variety of architectures](https://github.com/docker-library/official-images#architectures-other-than-amd64){:target="blank" rel="noopener" class=""}. For example, the `busybox` image supports `amd64`, `arm32v5`, `arm32v6`, @@ -23,55 +23,61 @@ on an `x86_64` / `amd64` machine, the `amd64` variant is pulled and run. ## Building multi-platform images -Docker is now making it easier than ever to develop containers on, and for Arm -servers and devices. Using the standard Docker tooling and processes, you can -start to build, push, pull, and run images seamlessly on different compute -architectures. In most cases, you don't have to make any changes to Dockerfiles -or source code to start building for Arm. - -BuildKit with Buildx is designed to work well for building for multiple -platforms and not only for the architecture and operating system that the user -invoking the build happens to run. - When you invoke a build, you can set the `--platform` flag to specify the target -platform for the build output, (for example, `linux/amd64`, `linux/arm64`, or -`darwin/amd64`). +platform for the build output. For example, `linux/amd64`, `linux/arm64`, or +`darwin/amd64`. -When the current builder instance is backed by the `docker-container` driver, -you can specify multiple platforms together. In this case, it builds a manifest -list which contains images for all specified architectures. When you use this -image in [`docker run`](../../engine/reference/commandline/run.md) or -[`docker service`](../../engine/reference/commandline/service.md), Docker picks -the correct image based on the node's platform. +By default, you can only build for a single platform at a time. If you want to +build for multiple platforms at once, you can: -You can build multi-platform images using three different strategies that are -supported by Buildx and Dockerfiles: +- Create a new builder that uses the [`docker-container` driver](../drivers/docker-container.md) +- Turn on the [containerd snapshotter storage](../../desktop/containerd/index.md) -1. Using the QEMU emulation support in the kernel -2. Building on multiple native nodes using the same builder instance -3. Using a stage in Dockerfile to cross-compile to different architectures +## Strategies -QEMU is the easiest way to get started if your node already supports it (for -example. if you are using Docker Desktop). It requires no changes to your -Dockerfile and BuildKit automatically detects the secondary architectures that -are available. When BuildKit needs to run a binary for a different architecture, -it automatically loads it through a binary registered in the `binfmt_misc` -handler. +You can build multi-platform images using three different strategies, +depending on your use case: + +1. Using the [QEMU emulation](#qemu) support in the kernel +2. Building on [multiple native nodes](#multiple-native-nodes) using the same + builder instance +3. Using a stage in your Dockerfile to [cross-compile](#cross-compilation) to + different architectures + +### QEMU + +Building multi-platform images under emulation with QEMU is the easiest way to +get started if your builder already supports it. Docker Desktop supports it out +of the box. It requires no changes to your Dockerfile, and BuildKit +automatically detects the secondary architectures that are available. When +BuildKit needs to run a binary for a different architecture, it automatically +loads it through a binary registered in the `binfmt_misc` handler. + +> **Note** +> +> QEMU performs full-system emulation of non-native platforms, which is much +> slower than native builds. Compute-heavy tasks like compilation and +> compression or decompression likely results in a large performance hit. +> +> Use [cross-compilation](#cross-compilation) instead, if possible. For QEMU binaries registered with `binfmt_misc` on the host OS to work -transparently inside containers, they must be statically compiled and registered -with the `fix_binary` flag. This requires a kernel >= 4.8 and -binfmt-support >= 2.1.7. You can check for proper registration by checking if -`F` is among the flags in `/proc/sys/fs/binfmt_misc/qemu-*`. While Docker -Desktop comes preconfigured with `binfmt_misc` support for additional platforms, -for other installations it likely needs to be installed using -[`tonistiigi/binfmt`](https://github.com/tonistiigi/binfmt){:target="blank" rel="noopener" class=""} -image. +transparently inside containers, they must be statically compiled and +registered with the `fix_binary` flag. This requires a kernel version 4.8 or +later, and `binfmt-support` version 2.1.7 or later. + +You can verify your registration by checking if `F` is among the flags in +`/proc/sys/fs/binfmt_misc/qemu-*`. While Docker Desktop comes preconfigured +with `binfmt_misc` support for additional platforms, for other installations it +likely needs to be installed using +[`tonistiigi/binfmt`](https://github.com/tonistiigi/binfmt) image: ```console $ docker run --privileged --rm tonistiigi/binfmt --install all ``` +### Multiple native nodes + Using multiple native nodes provide better support for more complicated cases that are not handled by QEMU and generally have better performance. You can add additional nodes to the builder instance using the `--append` flag. @@ -85,13 +91,18 @@ $ docker buildx create --append --name mybuild node-arm64 $ docker buildx build --platform linux/amd64,linux/arm64 . ``` -Finally, depending on your project, the language that you use may have good -support for cross-compilation. In that case, multi-stage builds in Dockerfiles -can be effectively used to build binaries for the platform specified with -`--platform` using the native architecture of the build node. A list of build -arguments like `BUILDPLATFORM` and `TARGETPLATFORM` is available automatically -inside your Dockerfile and can be leveraged by the processes running as part -of your build. +For information on using multiple native nodes in CI, with GitHub Actions, +refer to +[Configure your GitHub Actions builder](../ci/github-actions/configure-builder.md#append-additional-nodes-to-the-builder). + +### Cross-compilation + +Depending on your project, if the programming language you use has good support +for cross-compilation, multi-stage builds in Dockerfiles can be effectively +used to build binaries for target platforms using the native architecture of +the build node. Build arguments such as `BUILDPLATFORM` and `TARGETPLATFORM` +are automatically available for use in your Dockerfile, and can be leveraged by +the processes running as part of your build. ```dockerfile # syntax=docker/dockerfile:1 @@ -124,34 +135,7 @@ and the more advanced cache exporters, which are currently unsupported in the default `docker` driver: ```console -$ docker buildx create --name mybuilder --driver docker-container --bootstrap -mybuilder -``` - -Switch to the new builder: - -```console -$ docker buildx use mybuilder -``` - -> **Note** -> -> Alternatively, run `docker buildx create --name mybuilder --driver docker-container --bootstrap --use` -> to create a new builder and switch to it using a single command. - -And inspect it: - -```console -$ docker buildx inspect -Name: mybuilder -Driver: docker-container - -Nodes: -Name: mybuilder0 -Endpoint: unix:///var/run/docker.sock -Status: running -Buildkit: v0.10.4 -Platforms: linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6 +$ docker buildx create --name mybuilder --bootstrap --use ``` Now listing the existing builders again, we can see our new builder is @@ -160,10 +144,10 @@ registered: ```console $ docker buildx ls NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS -mybuilder docker-container - mybuilder0 unix:///var/run/docker.sock running v0.10.4 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6 -default * docker - default default running v0.11.6 linux/amd64, linux/arm64, linux/arm/v7, linux/arm/v6 +mybuilder * docker-container + mybuilder0 unix:///var/run/docker.sock running v0.12.1 linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/mips64le, linux/mips64, linux/arm/v7, linux/arm/v6 +default docker + default default running v{{ site.buildkit_version }} linux/amd64, linux/arm64, linux/arm/v7, linux/arm/v6 ``` ## Example @@ -272,5 +256,5 @@ Linux architectures such as `arm`, `mips`, `ppc64le`, and even `s390x`. This does not require any special configuration in the container itself as it uses [qemu-static](https://wiki.qemu.org/Main_Page){:target="blank" rel="noopener" class=""} -from the **Docker for Mac VM**. Because of this, you can run an ARM container, +from the Docker Desktop VM. Because of this, you can run an ARM container, like the `arm32v7` or `ppc64le` variants of the busybox image. diff --git a/build/building/multi-stage.md b/build/building/multi-stage.md index 254ee74224..065a92555c 100644 --- a/build/building/multi-stage.md +++ b/build/building/multi-stage.md @@ -1,6 +1,8 @@ --- title: Multi-stage builds -description: Keeping your images small with multi-stage builds +description: | + Learn about multi-stage builds and how you can use + them to improve your builds and get smaller images keywords: build, best practices redirect_from: - /engine/userguide/eng-image/multistage-build/ @@ -10,125 +12,46 @@ redirect_from: Multi-stage builds are useful to anyone who has struggled to optimize Dockerfiles while keeping them easy to read and maintain. -> **Acknowledgment** -> -> Special thanks to [Alex Ellis](https://twitter.com/alexellisuk){:target="blank" rel="noopener" class=""} -> for granting permission to use his blog post [Builder pattern vs. Multi-stage builds in Docker](https://blog.alexellis.io/mutli-stage-docker-builds/){:target="blank" rel="noopener" class=""} -> as the basis of the examples on this page. - -## Before multi-stage builds - -One problem you may face as you build and publish images, is that the size of -those images can sometimes grow quite large. Traditionally, before multi-stage -builds were a thing, keeping the size of images down would require you to -manually clean up resources from the image, so as to keep it small. - -In the past, it was common practice to have one Dockerfile for development, -and another, slimmed-down one to use for production. -The development version contained everything needed to build your application. -The production version only contained your application -and the dependencies needed to run it. - -To write a truly efficient Dockerfile, you had to come up with shell tricks and -arcane solutions to keep the layers as small as possible. -All to ensure that each layer contained only the artifacts it needed, -and nothing else. - -This has been referred to as the _builder pattern_. -The following examples show two Dockerfiles that adhere to this pattern: - -- `build.Dockerfile`, for development builds -- `Dockerfile`, for slimmed-down production builds - -**`build.Dockerfile`**: - -```dockerfile -# syntax=docker/dockerfile:1 -FROM golang:1.16 -WORKDIR /go/src/github.com/alexellis/href-counter/ -COPY app.go ./ -RUN go get -d -v golang.org/x/net/html \ - && CGO_ENABLED=0 go build -a -installsuffix cgo -o app . -``` - -Notice how this example artificially compresses two `RUN` commands together -using the Bash `&&` operator. This is done to avoid creating an additional -layer in the image. Writing Dockerfiles like this is failure-prone and hard to -maintain. It's easy to insert another command and forget to continue the line -using the `\` character, for example. - -**`Dockerfile`**: - -```dockerfile -# syntax=docker/dockerfile:1 -FROM alpine:latest -RUN apk --no-cache add ca-certificates -WORKDIR /root/ -COPY app ./ -CMD ["./app"] -``` - -The following example is a utility script that: - -1. Builds the first image. -2. Creates a container from it to copy the artifact out. -3. Builds the second image. - -```bash -#!/bin/sh -echo Building alexellis2/href-counter:build -docker build -t alexellis2/href-counter:build . -f build.Dockerfile - -docker container create --name extract alexellis2/href-counter:build -docker container cp extract:/go/src/github.com/alexellis/href-counter/app ./app -docker container rm -f extract - -echo Building alexellis2/href-counter:latest -docker build --no-cache -t alexellis2/href-counter:latest . -rm ./app -``` - -Both images take up room on your system and you still end up with the `app` -artifact on your local disk as well. - -Multi-stage builds simplifies this situation! - ## Use multi-stage builds With multi-stage builds, you use multiple `FROM` statements in your Dockerfile. Each `FROM` instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to -another, leaving behind everything you don't want in the final image. To show -how this works, you can adapt the `Dockerfile` from the previous section to use -multi-stage builds. +another, leaving behind everything you don't want in the final image. + +The following Dockerfile has two separate stages: one for building a binary, +and another where we copy the binary into. ```dockerfile # syntax=docker/dockerfile:1 +FROM golang:1.21 +WORKDIR /src +COPY <`](../../engine/reference/builder.md#from) | Defines a base for your image. | | [`RUN `](../../engine/reference/builder.md#run) | Executes any commands in a new layer on top of the current image and commits the result. `RUN` also has a shell form for running commands. | | [`WORKDIR `](../../engine/reference/builder.md#workdir) | Sets the working directory for any `RUN`, `CMD`, `ENTRYPOINT`, `COPY`, and `ADD` instructions that follow it in the Dockerfile. | @@ -26,35 +26,38 @@ Here are the most common types of instructions: Dockerfiles are crucial inputs for image builds and can facilitate automated, multi-layer image builds based on your unique configurations. Dockerfiles can -start simple and grow with your needs and support images that require complex -instructions. For all the possible instructions, see the [Dockerfile reference](../../engine/reference/builder.md). +start simple and grow with your needs to support more complex scenarios. + +### Filename The default filename to use for a Dockerfile is `Dockerfile`, without a file extension. Using the default name allows you to run the `docker build` command without having to specify additional command flags. Some projects may need distinct Dockerfiles for specific purposes. A common -convention is to name these `.Dockerfile`. Such Dockerfiles can then -be used through the `--file` (or `-f` shorthand) option on the `docker build` command. -Refer to the ["Specify a Dockerfile" section](../../engine/reference/commandline/build.md#file) -in the `docker build` reference to learn about the `--file` option. +convention is to name these `.Dockerfile`. You can specify the +Dockerfile filename using the `--file` flag for the `docker build` command. +Refer to the +[`docker build` CLI reference](../../engine/reference/commandline/build.md#file) +to learn about the `--file` flag. > **Note** > > We recommend using the default (`Dockerfile`) for your project's primary > Dockerfile. -Docker images consist of **read-only layers**, each resulting from an -instruction in the Dockerfile. Layers are stacked sequentially and each one is +## Docker images + +Docker images consist of layers. Each layer is the result of a build +instruction in the Dockerfile. Layers are stacked sequentially, and each one is a delta representing the changes applied to the previous layer. -## Example +### Example -Here's a simple Dockerfile example to get you started with building images. -We'll take a simple "Hello World" Python Flask application, and bundle it into -a Docker image that can test locally or deploy anywhere! +Here's what a typical workflow for building applications with Docker looks like. -Let's say we have a `hello.py` file with the following content: +The following example code shows a small "Hello World" application written in +Python, using the Flask framework. ```python from flask import Flask @@ -65,18 +68,15 @@ def hello(): return "Hello World!" ``` -Don't worry about understanding the full example if you're not familiar with -Python, it's just a simple web server that will contain a single page that -says "Hello World". +In order to ship and deploy this application without Docker Build, you would +need to make sure that: -> **Note** -> -> If you test the example, make sure to copy over the indentation as well! For -> more information about this sample Flask application, check the -> [Flask Quickstart](https://flask.palletsprojects.com/en/2.1.x/quickstart/){:target="blank" rel="noopener" class=""} -> page. +- The required runtime dependencies are installed on the server +- The Python code gets uploaded to the server's filesystem +- The server starts your application, using the necessary parameters -Here's the Dockerfile that will be used to create an image for our application: +The following Dockerfile creates a container image, which has all the +dependencies installed and that automatically starts your application. ```dockerfile # syntax=docker/dockerfile:1 @@ -95,6 +95,20 @@ EXPOSE 8000 CMD flask run --host 0.0.0.0 --port 8000 ``` +Here's a breakdown of what this Dockerfile does: + +- [Dockerfile syntax](#dockerfile-syntax) +- [Base image](#base-image) +- [Environment setup](#environment-setup) +- [Comments](#comments) +- [Installing dependencies](#installing-dependencies) +- [Copying files](#copying-files) +- [Setting environment variables](#setting-environment-variables) +- [Exposed ports](#exposed-ports) +- [Starting the application](#starting-the-application) + +### Dockerfile syntax + The first line to add to a Dockerfile is a [`# syntax` parser directive](../../engine/reference/builder.md#syntax). While optional, this directive instructs the Docker builder what syntax to use when parsing the Dockerfile, and allows older Docker versions with [BuildKit enabled](../buildkit/index.md#getting-started) @@ -107,128 +121,156 @@ your Dockerfile, and should be the first line in Dockerfiles. # syntax=docker/dockerfile:1 ``` -> **Note** +> **Tip** > > We recommend using `docker/dockerfile:1`, which always points to the latest > release of the version 1 syntax. BuildKit automatically checks for updates of > the syntax before building, making sure you are using the most current version. +{: .tip } -Next we define the first instruction: +### Base image + +The line following the syntax directive defines what base image to use: ```dockerfile FROM ubuntu:22.04 ``` -Here the [`FROM` instruction](../../engine/reference/builder.md#from) sets our -base image to the 22.04 release of Ubuntu. All following instructions are -executed on this base image, in this case, an Ubuntu environment. The notation -`ubuntu:22.04`, follows the `name:tag` standard for naming docker images. When -you build your image you use this notation to name your images and use it to -specify any existing Docker image. There are many public images you can -leverage in your projects. Explore [Docker Hub](https://hub.docker.com/search?image_filter=official&q=&type=image){:target="blank" rel="noopener" class=""} -to find out. +The [`FROM` instruction](../../engine/reference/builder.md#from) sets your base +image to the 22.04 release of Ubuntu. All instructions that follow are executed +in this base image: an Ubuntu environment. The notation `ubuntu:22.04`, follows +the `name:tag` standard for naming Docker images. When you build images, you +use this notation to name your images. There are many public images you can +leverage in your projects, by importing them into your build steps using the +Dockerfile `FROM` instruction. + +[Docker Hub](https://hub.docker.com/search?image_filter=official&q=&type=image) +contains a large set of official images that you can use for this purpose. + +### Environment setup + +The following line executes a build command inside the base image. ```dockerfile # install app dependencies RUN apt-get update && apt-get install -y python3 python3-pip ``` -This [`RUN` instruction](../../engine/reference/builder.md#run) executes a shell -command in the [build context](context.md). +This [`RUN` instruction](../../engine/reference/builder.md#run) executes a +shell in Ubuntu that updates the APT package index and installs Python tools in +the container. -In this example, our context is a full Ubuntu operating system, so we have -access to its package manager, apt. The provided commands update our package -lists and then, after that succeeds, installs `python3` and `pip`, the package -manager for Python. +### Comments -Also note `# install app dependencies` line. This is a comment. Comments in +Note the `# install app dependencies` line. This is a comment. Comments in Dockerfiles begin with the `#` symbol. As your Dockerfile evolves, comments can -be instrumental to document how your dockerfile works for any future readers -and editors of the file. +be instrumental to document how your Dockerfile works for any future readers +and editors of the file, including your future self! > **Note** > -> Starting your Dockerfile by a `#` like regular comments is treated as a -> directive when you are using BuildKit (default), otherwise it is ignored. +> You might've noticed that comments are denoted using the same symbol as the +> [syntax directive](#dockerfile-syntax) on the first line of the file. +> The symbol is only interpreted as a directive if the pattern matches a +> directive and appears at the beginning of the Dockerfile. Otherwise, it's +> treated as a comment. + +### Installing dependencies + +The second `RUN` instruction installs the `flask` dependency required by the +Python application. ```dockerfile RUN pip install flask==2.1.* ``` -This second `RUN` instruction requires that we've installed pip in the layer -before. After applying the previous directive, we can use the pip command to -install the flask web framework. This is the framework we've used to write -our basic "Hello World" application from above, so to run it in Docker, we'll -need to make sure it's installed. +A prerequisite for this instruction is that `pip` is installed into the build +container. The first `RUN` command installs `pip`, which ensures that we can +use the command to install the flask web framework. + +### Copying files + +The next instruction uses the +[`COPY` instruction](../../engine/reference/builder.md#copy) to copy the +`hello.py` file from the local build context into the root directory of our image. ```dockerfile COPY hello.py / ``` -Now we use the [`COPY` instruction](../../engine/reference/builder.md#copy) to -copy our `hello.py` file from the local [build context](context.md) into the -root directory of our image. After being executed, we'll end up with a file -called `/hello.py` inside the image. +A [build context](context.md) is the set of files that you can access +in Dockerfile instructions such as `COPY` and `ADD`. + +After the `COPY` instruction, the `hello.py` file is added to the filesystem +of the build container. + +### Setting environment variables + +If your application uses environment variables, you can set environment variables +in your Docker build using the [`ENV` instruction](../../engine/reference/builder.md#env). ```dockerfile ENV FLASK_APP=hello ``` -This [`ENV` instruction](../../engine/reference/builder.md#env) sets a Linux -environment variable we'll need later. This is a flask-specific variable, -that configures the command later used to run our `hello.py` application. -Without this, flask wouldn't know where to find our application to be able to -run it. +This sets a Linux environment variable we'll need later. Flask, the framework +used in this example, uses this variable to start the application. Without this, +flask wouldn't know where to find our application to be able to run it. + +### Exposed ports + +The [`EXPOSE` instruction](../../engine/reference/builder.md#expose) marks that +our final image has a service listening on port `8000`. ```dockerfile EXPOSE 8000 ``` -This [`EXPOSE` instruction](../../engine/reference/builder.md#expose) marks that -our final image has a service listening on port `8000`. This isn't required, -but it is a good practice, as users and tools can use this to understand what -your image does. +This instruction isn't required, but it is a good practice and helps tools and +team members understand what this application is doing. + +### Starting the application + +Finally, [`CMD` instruction](../../engine/reference/builder.md#cmd) sets the +command that is run when the user starts a container based on this image. ```dockerfile CMD flask run --host 0.0.0.0 --port 8000 ``` -Finally, [`CMD` instruction](../../engine/reference/builder.md#cmd) sets the -command that is run when the user starts a container based on this image. In -this case we'll start the flask development server listening on all addresses +In this case we'll start the flask development server listening on all addresses on port `8000`. -## Testing +## Building -To test our Dockerfile, we'll first build it using the [`docker build` command](../../engine/reference/commandline/build.md): +To build a container image using the Dockerfile example from the +[previous section](#example), you use the `docker build` command: ```console $ docker build -t test:latest . ``` -Here `-t test:latest` option specifies the name (required) and tag (optional) -of the image we're building. `.` specifies the [build context](context.md) as -the current directory. In this example, this is where build expects to find the -Dockerfile and the local files the Dockerfile needs to access, in this case -your Python application. +The `-t test:latest` option specifies the name and tag of the image. -So, in accordance with the build command issued and how [build context](context.md) -works, your Dockerfile and python app need to be in the same directory. +The single dot (`.`) at the end of the command sets the +[build context](context.md) to the current directory. This means that the +build expects to find the Dockerfile and the `hello.py` file in the directory +where the command is invoked. If those files aren't there, the build fails. -Now run your newly built image: +After the image has been built, you can run the application as a container with +`docker run`, specifying the image name: ```console -$ docker run -p 8000:8000 test:latest +$ docker run -p 127.0.0.1:8000:8000 test:latest ``` -From your computer, open a browser and navigate to `http://localhost:8000` - -> **Note** -> -> You can also build and run using [Play with Docker](https://labs.play-with-docker.com){:target="blank" rel="noopener" class=""} -> that provides you with a temporary Docker instance in the cloud. +This publishes the container's port 8000 to `http://localhost:8000` on the +Docker host. ## Other resources If you are interested in examples in other languages, such as Go, check out our [language-specific guides](../../language/index.md) in the Guides section. + +For more information about building, including advanced use cases and patterns, +refer to the [Build with Docker](../guide/index.md) guide. diff --git a/build/ci/github-actions/configure-builder.md b/build/ci/github-actions/configure-builder.md index 2a137f6ee3..bcff323e5f 100644 --- a/build/ci/github-actions/configure-builder.md +++ b/build/ci/github-actions/configure-builder.md @@ -1,6 +1,6 @@ --- -title: Configuring your builder -description: Configuring BuildKit instances with GitHub Actions. +title: Configuring your GitHub Actions builder +description: Configuring BuildKit instances for building in CI with GitHub Actions keywords: ci, github actions, gha, buildkit, buildx --- diff --git a/build/index.md b/build/index.md index 4f066b7bef..f086d4d38b 100644 --- a/build/index.md +++ b/build/index.md @@ -7,15 +7,14 @@ redirect_from: - /develop/develop-images/build_enhancements/ --- -Docker Build is one of Docker Engine's most used features. Whenever you are -creating an image you are using Docker Build. Build is a key part of your +Docker Build is one of Docker Engine's most used features. Whenever you're +creating an image, you are using Docker Build. Build is a key part of your software development life cycle allowing you to package and bundle your code and ship it anywhere. Docker Build is more than a command for building images, and it's not only about packaging your code. It's a whole ecosystem of tools and features that support -not only common workflow tasks but also provides support for more complex and -advanced scenarios. +both common workflow tasks and more complex and advanced scenarios.