build: added guide for docker build

Signed-off-by: David Karlsson <david.karlsson@docker.com>
This commit is contained in:
David Karlsson 2023-04-03 17:13:57 +02:00
parent 178972a3d5
commit da6586c498
21 changed files with 14119 additions and 0 deletions

View File

@ -53,6 +53,10 @@ distribution_version: "2.7"
compose_switch_version: "1.0.4"
buildkit_version: "0.10.5"
# Strings for use in doc examples, e.g. runtime versions.
example_go_version: "1.20"
example_golangci_lint_version: "v1.52"
# Options for displaying minimum API version requirements in the reference pages.
#
# The reference pages show badges for commands and options (flags) that require

View File

@ -155,6 +155,30 @@ guides:
title: Security best practices
- path: /develop/remote-development/
title: Remote development
- sectiontitle: Build with Docker
section:
- path: /build/guide/
title: Overview
- path: /build/guide/intro/
title: 1. Introduction
- path: /build/guide/layers/
title: 2. Layers
- path: /build/guide/multi-stage/
title: 3. Multi-stage
- path: /build/guide/mounts/
title: 4. Mounts
- path: /build/guide/build-args/
title: 5. Build arguments
- path: /build/guide/export/
title: 6. Export binaries
- path: /build/guide/test/
title: 7. Test
- path: /build/guide/multi-platform/
title: 8. Multi-platform
- path: /build/guide/next-steps/
title: Next steps
- sectiontitle: Deploy your app to the cloud
section:
- path: /cloud/aci-integration/

156
build/guide/build-args.md Normal file
View File

@ -0,0 +1,156 @@
---
title: Build arguments
description: Introduction to configurable builds, using build args
keywords: >
build, buildkit, buildx, guide, tutorial, build arguments, arg
---
{% include_relative nav.html selected="5" %}
Build arguments is a great way to add flexibility to your builds. You can pass
build arguments at build-time, and you can set a default value that the builder
uses as a fallback.
## Change runtime versions
A practical use case for build arguments is to specify runtime versions for
build stages. Your image uses the `golang:{{site.example_go_version}}-alpine`
image as a base image.
But what if someone wanted to use a different version of Go for building the
application? They could update the version number inside the Dockerfile, but
thats inconvenient, it makes switching between versions more tedious than it
has to be. Build arguments make life easier:
```diff
# syntax=docker/dockerfile:1
- FROM golang:{{site.example_go_version}}-alpine AS base
+ ARG GO_VERSION={{site.example_go_version}}
+ FROM golang:${GO_VERSION}-alpine AS base
WORKDIR /src
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,source=go.sum,target=go.sum \
--mount=type=bind,source=go.mod,target=go.mod \
go mod download -x
FROM base AS build-client
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,target=. \
go build -o /bin/client ./cmd/client
FROM base AS build-server
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,target=. \
go build -o /bin/server ./cmd/server
FROM scratch AS client
COPY --from=build /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]
FROM scratch AS server
COPY --from=build /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
```
The `ARG` keyword is interpolated in the image name in the `FROM` instruction.
The default value of the `GO_VERSION` build argument is set to `{{site.example_go_version}}`.
If the build doesn't receive a `GO_VERSION` build argument, the `FROM` instruction
resolves to `golang:{{site.example_go_version}}-alpine`.
Try setting a different version of Go to use for building, using the
`--build-arg` flag for the build command:
```console
$ docker build --build-arg="GO_VERSION=1.19" .
```
Running this command results in a build using the `golang:1.19-alpine` image.
## Inject values
You can also make use of build arguments to modify values in the source code of
your program, at build time. This is useful for dynamically injecting
information, avoiding hard-coded values. With Go, consuming external values at
build time is done using linker flags, or `-ldflags`.
The server part of the application contains a conditional statement to print the
app version, if a version is specified:
```go
// cmd/server/main.go
var version string
func main() {
if version != "" {
log.Printf("Version: %s", version)
}
```
You could declare the version string value directly in the code. But, updating
the version to line up with the release version of the application would require
updating the code ahead of every release. That would be both tedious and
error-prone. A better solution is to pass the version string as a build
argument, and inject the build argument into the code.
The following example adds an `APP_VERSION` build argument to the `build-server`
stage. The Go compiler uses the value of the build argument to set the value of
a variable in the code.
```diff
# syntax=docker/dockerfile:1
ARG GO_VERSION={{site.example_go_version}}
FROM golang:${GO_VERSION}-alpine AS base
WORKDIR /src
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,source=go.sum,target=go.sum \
--mount=type=bind,source=go.mod,target=go.mod \
go mod download -x
FROM base AS build-client
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,target=. \
go build -o /bin/client ./cmd/client
FROM base AS build-server
+ ARG APP_VERSION="v0.0.0+unknown"
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,target=. \
- go build -o /bin/server ./cmd/server
+ go build -ldflags "-X main.version=$APP_VERSION" -o /bin/server ./cmd/server
FROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]
FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
```
Now the version of the server when building the binary, without having to update
the source code. To verify this, you can the `server` target and start a
container with `docker run`. The server outputs `v0.0.1` as the version on
startup.
```console
$ docker build --target=server --build-arg="APP_VERSION=v0.0.1" --tag=buildme-server .
$ docker run buildme-server
2023/04/06 08:54:27 Version: v0.0.1
2023/04/06 08:54:27 Starting server...
2023/04/06 08:54:27 Listening on HTTP port 3000
```
## Summary
This section showed how you can use build arguments to make builds more
configurable, and inject values at build-time.
Related information:
- [`ARG` Dockerfile reference](../../engine/reference/builder.md#arg)
## Next steps
The next section of this guide shows how you can use Docker builds to create not
only container images, but executable binaries as well.
[Export binaries](export.md){: .button .primary-btn }

117
build/guide/export.md Normal file
View File

@ -0,0 +1,117 @@
---
title: Export binaries
description: Using Docker builds to create and export executable binaries
keywords: >
build, buildkit, buildx, guide, tutorial, build arguments, arg
---
{% include_relative nav.html selected="6" %}
Did you know that you can use Docker to build your application to standalone
binaries? Sometimes, you dont want to package and distribute your application
as a Docker image. Use Docker to build your application, and use exporters to
save the output to disk.
The default output format for `docker build` is a container image. That image is
automatically loaded to your local image store, where you can run a container
from that image, or push it to a registry. Under the hood, this uses the default
exporter, called the `docker` exporter.
To export your build results as files instead, you can use the `local` exporter.
The `local` exporter saves the filesystem of the build container to the
specified directory on the host machine.
## Export binaries
To use the `local` exporter, pass the `--output` option to the `docker build`
command. The `--output` flag takes one argument: the destination on the host
machine where you want to save the files.
The following commands exports the files from of the `server` target to the
current working directory on the host filesystem:
```console
$ docker build --output=. --target=server .
```
Running this command creates a binary at `./bin/server`. Its created under the
`bin/` directory because thats where the file was located inside the build
container.
```console
$ ls -l ./bin
total 14576
-rwxr-xr-x 1 user user 7459368 Apr 6 09:27 server
```
If you want to create a build that exports both binaries, you can create another
build stage in the Dockerfile that copies both of the binaries from each build
stage:
```diff
# syntax=docker/dockerfile:1
ARG GO_VERSION={{site.example_go_version}}
FROM golang:${GO_VERSION}-alpine AS base
WORKDIR /src
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,source=go.sum,target=go.sum \
--mount=type=bind,source=go.mod,target=go.mod \
go mod download -x
FROM base as build-client
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,target=. \
go build -o /bin/client ./cmd/client
FROM base as build-server
ARG APP_VERSION="0.0.0+unknown"
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,target=. \
go build -ldflags "-X main.version=$APP_VERSION" -o /bin/server ./cmd/server
FROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]
FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
+
+ FROM scratch AS binaries
+ COPY --from=build-client /bin/client /
+ COPY --from=build-server /bin/server /
```
Now you can build the `binaries` target using the `--output` option to export
both the client and server binaries.
```console
$ docker build --output=bin --target=binaries .
$ ls -l ./bin
total 29392
-rwxr-xr-x 1 user user 7581933 Apr 6 09:33 client
-rwxr-xr-x 1 user user 7459368 Apr 6 09:33 server
```
## Summary
This section has demonstrated how you can use Docker to build and export
standalone binaries. These binaries can be distributed freely, and dont require
a container runtime like the Docker daemon.
The binaries you've generated so far are Linux binaries. That's because the
build environment is Linux. If your host OS is Linux, you can run these files.
Building binaries that work on Mac or Windows machines requires cross-compilation.
This is explored later on in this guide.
Related information:
- [`docker build --output` CLI reference](../../engine/reference/commandline/build.md#output)
- [Build exporters](../exporters/index.md)
## Next steps
The next topic of this guide is testing: how you can use Docker to run
application tests.
[Test](test.md){: .button .primary-btn }

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

34
build/guide/index.md Normal file
View File

@ -0,0 +1,34 @@
---
title: Build with Docker
description: Explore the features of Docker Build in this step-by-step guide
keywords: build, buildkit, buildx, guide, tutorial
---
Welcome! This guide is an introduction and deep-dive into building software with
Docker.
Whether youre just getting started, or youre already an advanced Docker user,
this guide aims to provide useful pointers into the possibilities and best
practices of Docker's build features.
Topics covered in this guide include:
- Introduction to build concepts
- Image size optimization
- Build speed performance improvements
- Building and exporting binaries
- Cache mounts and bind mounts
- Software testing
- Multi-platform builds
Throughout this guide, an example application written in Go is used to
illustrate how the build features work. You dont need to know the Go
programming language to follow this guide.
The guide starts off with a simple Dockerfile example, and builds from there.
Some of the later sections in this guide describe advanced concepts and
workflows. You don't need to complete this entire guide from start to finish.
Follow the sections that seem relevant to you, and save the advanced sections at
the end for later, when you need them.
[Get started](intro.md){: .button .primary-btn }

172
build/guide/intro.md Normal file
View File

@ -0,0 +1,172 @@
---
title: Introduction
description: An introduction to the Docker Build guide
keywords: build, buildkit, buildx, guide, tutorial, introduction
---
{% include_relative nav.html selected="1" %}
The starting resources for this guide includes a simple Go project and a
Dockerfile. From this starting point, the guide illustrates various ways that
you can improve how you build the application with Docker.
## Environment setup
To follow this guide:
1. Install [Docker Desktop or Docker Engine](../../get-docker.md)
2. Clone or create a new repository from the
[application example on GitHub](https://github.com/dockersamples/buildme)
## The application
The example project for this guide is a client-server application for
translating messages to a fictional language.
Heres an overview of the files included in the project:
```text
.
├── Dockerfile
├── cmd
│   ├── client
│   │   ├── main.go
│   │   ├── request.go
│   │   └── ui.go
│   └── server
│   ├── main.go
│   └── translate.go
├── go.mod
└── go.sum
```
The `cmd/` directory contains the code for the two application components:
client and server. The client is a user interface for writing, sending, and
receiving messages. The server receives messages from clients, translates them,
and sends them back to the client.
## The Dockerfile
A Dockerfile is a text document in which you define the build steps for your
application. You write the Dockerfile in a domain-specific language, called the
Dockerfile syntax.
Here's the Dockerfile used as the starting point for this guide:
```dockerfile
# syntax=docker/dockerfile:1
FROM golang:{{site.example_go_version}}-alpine
WORKDIR /src
COPY . .
RUN go mod download
RUN go build -o /bin/client ./cmd/client
RUN go build -o /bin/server ./cmd/server
ENTRYPOINT [ "/bin/server" ]
```
Heres what this Dockerfile does:
1. `# syntax=docker/dockerfile:1`
This comment is a
[Dockerfile parser directive](../../engine/reference/builder.md#parser-directives).
It specifies which version of the Dockerfile syntax to use. This file uses
the `dockerfile:1` syntax which is best practice: it ensures that you have
access to the latest Docker build features.
2. `FROM golang:{{site.example_go_version}}-alpine`
The `FROM` instruction uses version `{{site.example_go_version}}-alpine` of the `golang` official image.
3. `WORKDIR /src`
Creates the `/src` working directory inside the container.
4. `COPY . .`
Copies the files in the build context to the working directory in the
container.
5. `RUN go mod download`
Downloads the necessary Go modules to the container. Go modules is the
dependency management tool for the Go programming language, similar to
`npm install` for JavaScript, or `pip install` for Python.
6. `RUN go build -o /bin/client ./cmd/client`
Builds the `client` binary, used to send messages to be translated, into the
`/bin` directory.
7. `RUN go build -o /bin/server ./cmd/server`
Builds the `server` binary, which listens for client translation requests,
into the `/bin` directory.
8. `ENTRYPOINT [ "/bin/server" ]`
Specifies a command to run when the container starts. Starts the server
process.
## Build the image
To build an image using a Dockerfile, you use the `docker` command-line tool.
The command for building an image is `docker build`.
Run the following command to build the image.
```console
$ docker build --tag=buildme .
```
This creates an image with the tag `buildme`. An image tag is the name of the
image.
## Run the container
The image you just built contains two binaries, one for the server and one for
the client. To see the translation service in action, run a container that hosts
the server component, and then run another container that invokes the client.
To run a container, you use the `docker run` command.
1. Run a container from the image in detached mode.
```console
$ docker run --name=buildme --rm --detach buildme
```
This starts a container named `buildme`.
2. Run a new command in the `buildme` container that invokes the client binary.
```console
$ docker exec -it buildme /bin/client
```
The `docker exec` command opens a terminal user interface where you can submit
messages for the backend (server) process to translate.
When you're done testing, you can stop the container:
```console
$ docker stop buildme
```
## Summary
This section gave you an overview of the example application used in this guide,
an introduction to Dockerfiles and building. You've successfully built a
container image and created a container from it.
Related information:
- [Dockerfile reference](../../engine/reference/builder.md)
- [`docker build` CLI reference](../../engine/reference/commandline/build.md)
- [`docker run` CLI reference](../../engine/reference/commandline/run.md)
## Next steps
The next section explores how you can use layer cache to improve build speed.
[Layers](layers.md){: .button .primary-btn }

83
build/guide/layers.md Normal file
View File

@ -0,0 +1,83 @@
---
title: Layers
description: Improving the initial Dockerfile using layers
keywords: build, buildkit, buildx, guide, tutorial, layers
---
{% include_relative nav.html selected="2" %}
The order of Dockerfile instructions matter. A Docker build consists of a series
of ordered build instructions. Each instruction in a Dockerfile roughly translates
to an image layer. The following diagram illustrates how a Dockerfile translates
into a stack of layers in a container image.
![From Dockerfile to layers](./images/layers.png){:.invertible}
## Cached layers
When you run a build, the builder attempts to reuse layers from earlier builds.
If a layer of an image is unchanged, then the builder picks it up from the build cache.
If a layer has changed since the last build, that layer, and all layers that follow, must be rebuilt.
The Dockerfile from the previous section copies all project files to the
container (`COPY . .`) and then downloads application dependencies in the
following step (`RUN go mod download`). If you were to change any of the project
files, that would invalidate the cache for the `COPY` layer. It also invalidates
the cache for all of the layers that follow.
![Layer cache is bust](./images/cache-bust.png){:.invertible}
The current order of the Dockerfile instruction make it so that the builder must
download the Go modules again, despite none of the packages having changed since
last time.
## Update the instruction order
You can avoid this redundancy by reordering the instructions in the Dockerfile.
Change the order of the instructions so that downloading and installing dependencies
occurs before you copy the source code over to the container. That way, the
builder can reuse the "dependencies" layer from the cache, even when you
make changes to your source code.
Go uses two files, called `go.mod` and `go.sum`, to track dependencies for a project.
These files are to Go, what `package.json` and `package-lock.json` are to JavaScript.
For Go to know which dependencies to download, you need to copy the `go.mod` and
`go.sum` files to the container. Add another `COPY` instruction before
`RUN go mod download`, this time copying only the `go.mod` and `go.sum` files.
```diff
# syntax=docker/dockerfile:1
FROM golang:{{site.example_go_version}}-alpine
WORKDIR /src
- COPY . .
+ COPY go.mod go.sum .
RUN go mod download
+ COPY . .
RUN go build -o /bin/client ./cmd/client
RUN go build -o /bin/server ./cmd/server
ENTRYPOINT [ "/bin/server" ]
```
Now if you edit the application code, building the image won't cause the
builder to download the dependencies each time. The `COPY . .` instruction
appears after the package management instructions, so the builder can reuse the
`RUN go mod download` layer.
![Reordered](./images/reordered-layers.png){:.invertible}
## Summary
Ordering your Dockerfile instructions appropriately helps you avoid unnecessary
work at build time.
Related information:
- [Optimizing builds with cache](../cache/index.md)
- [Dockerfile best practices](../../develop/develop-images/dockerfile_best-practices.md)
## Next steps
The next section shows how you can make the build run faster, and make the
resulting output smaller, using multi-stage builds.
[Multi-stage](multi-stage.md){: .button .primary-btn }

228
build/guide/mounts.md Normal file
View File

@ -0,0 +1,228 @@
---
title: Mounts
description: Introduction to cache mounts and bind mounts in builds
keywords: >
build, buildkit, buildx, guide, tutorial, mounts, cache mounts, bind mounts
---
{% include_relative nav.html selected="4" %}
This section describes how to use cache mounts and bind mounts with Docker
builds.
Cache mounts let you specify a persistent package cache to be used during
builds. The persistent cache helps speed up build steps, especially steps that
involve installing packages using a package manager. Having a persistent cache
for packages means that even if you rebuild a layer, you only download new or
changed packages.
Cache mounts are created using the `--mount` flag together with the `RUN`
instruction in the Dockerfile. To use a cache mount, the format for the flag is
`--mount=type=cache,target=<path>`, where `<path>` is the location of the cache
directory that you wish to mount into the container.
## Add a cache mount
The target path to use for the cache mount depends on the package manager youre
using. The application example in this guide uses Go modules. That means that
the target directory for the cache mount is the directory where the Go module
cache gets written to. According to the
[Go modules reference](https://go.dev/ref/mod#module-cache), the default
location for the module cache is `$GOPATH/pkg/mod`, and the default value for
`$GOPATH` is `/go`.
Update the build steps for downloading packages and compiling the program to
mount the `/go/pkg/mod` directory as a cache mount:
```diff
# syntax=docker/dockerfile:1
FROM golang:{{site.example_go_version}}-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
- RUN go mod download
+ RUN --mount=type=cache,target=/go/pkg/mod/ \
+ go mod download -x
COPY . .
FROM base AS build-client
- RUN go build -o /bin/client ./cmd/client
+ RUN --mount=type=cache,target=/go/pkg/mod/ \
+ go build -o /bin/client ./cmd/client
FROM base AS build-server
- RUN go build -o /bin/server ./cmd/server
+ RUN --mount=type=cache,target=/go/pkg/mod/ \
+ go build -o /bin/server ./cmd/server
FROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]
FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
```
The `-x` flag added to the `go mod download` command prints the download
executions that take place. Adding this flag lets you see how the cache mount is
being used in the next step.
## Rebuild the image
Before you rebuild the image, clear your build cache. This ensures that you're
starting from a clean slate, making it easier to see exactly what the build is
doing.
```console
$ docker builder prune -af
```
Now its time to rebuild the image. Invoke the build command, this time together
with the `--progress=plain` flag, while also redirecting the output to a log
file.
```console
$ docker build --target=client --progress=plain . 2> log1.txt
```
When the build has finished, inspect the `log1.txt` file. The logs show how the
Go modules were downloaded as part of the build.
```console
$ awk '/proxy.golang.org/' log1.txt
#11 0.168 # get https://proxy.golang.org/github.com/charmbracelet/lipgloss/@v/v0.6.0.mod
#11 0.168 # get https://proxy.golang.org/github.com/aymanbagabas/go-osc52/@v/v1.0.3.mod
#11 0.168 # get https://proxy.golang.org/github.com/atotto/clipboard/@v/v0.1.4.mod
#11 0.168 # get https://proxy.golang.org/github.com/charmbracelet/bubbletea/@v/v0.23.1.mod
#11 0.169 # get https://proxy.golang.org/github.com/charmbracelet/bubbles/@v/v0.14.0.mod
#11 0.218 # get https://proxy.golang.org/github.com/charmbracelet/bubbles/@v/v0.14.0.mod: 200 OK (0.049s)
#11 0.218 # get https://proxy.golang.org/github.com/aymanbagabas/go-osc52/@v/v1.0.3.mod: 200 OK (0.049s)
#11 0.218 # get https://proxy.golang.org/github.com/containerd/console/@v/v1.0.3.mod
#11 0.218 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.0.mod
#11 0.219 # get https://proxy.golang.org/github.com/charmbracelet/bubbletea/@v/v0.23.1.mod: 200 OK (0.050s)
#11 0.219 # get https://proxy.golang.org/github.com/atotto/clipboard/@v/v0.1.4.mod: 200 OK (0.051s)
#11 0.219 # get https://proxy.golang.org/github.com/charmbracelet/lipgloss/@v/v0.6.0.mod: 200 OK (0.051s)
...
```
Now, in order to see that the cache mount is being used, change the version of
one of the Go modules that your program imports. By changing the module version,
you're forcing Go to download the new version of the dependency the next time
you build. If you werent using cache mounts, your system would re-download all
modules. But because you've added a cache mount, Go can reuse most of the
modules and only download the package versions that doesn't already exist in the
`/go/pkg/mod` directory.
Update the version of the `chi` package that the server component of the
application uses:
```console
$ docker run -v $PWD:$PWD -w $PWD golang:{{site.example_go_version}}-alpine \
go get github.com/go-chi/chi/v5@v5.0.8
```
Now, run another build, and again redirect the build logs to a log file:
```console
$ docker build --target=client --progress=plain . 2> log2.txt
```
Now if you inspect the `log2.txt` file, youll find that only the `chi` package
that was changed has been downloaded:
```console
$ awk '/proxy.golang.org/' log2.txt
#10 0.143 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.mod
#10 0.190 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.mod: 200 OK (0.047s)
#10 0.190 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.info
#10 0.199 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.info: 200 OK (0.008s)
#10 0.201 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.zip
#10 0.209 # get https://proxy.golang.org/github.com/go-chi/chi/v5/@v/v5.0.8.zip: 200 OK (0.008s)
```
## Add bind mounts
There are a few more small optimizations that you can implement to improve the
Dockerfile. Currently, it's using the `COPY` instruction to pull in the `go.mod`
and `go.sum` files before downloading modules. Instead of copying those files
over to the containers filesystem, you can use a bind mount. A bind mount makes
the files available to the container directly from the host. This change removes
the need for the additional `COPY` instruction (and layer) entirely.
```diff
# syntax=docker/dockerfile:1
FROM golang:{{site.example_go_version}}-alpine AS base
WORKDIR /src
- COPY go.mod go.sum .
RUN --mount=type=cache,target=/go/pkg/mod/ \
+ --mount=type=bind,source=go.sum,target=go.sum \
+ --mount=type=bind,source=go.mod,target=go.mod \
go mod download -x
COPY . .
FROM base AS build-client
RUN --mount=type=cache,target=/go/pkg/mod/ \
go build -o /bin/client ./cmd/client
FROM base AS build-server
RUN --mount=type=cache,target=/go/pkg/mod/ \
go build -o /bin/server ./cmd/server
FROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]
FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
```
Similarly, you can use the same technique to remove the need for the second
`COPY` instruction as well. Specify bind mounts in the `build-client` and
`build-server` stages for mounting the current working directory.
```diff
# syntax=docker/dockerfile:1
FROM golang:{{site.example_go_version}}-alpine AS base
WORKDIR /src
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,source=go.sum,target=go.sum \
--mount=type=bind,source=go.mod,target=go.mod \
go mod download -x
- COPY . .
FROM base AS build-client
RUN --mount=type=cache,target=/go/pkg/mod/ \
+ --mount=type=bind,target=. \
go build -o /bin/client ./cmd/client
FROM base AS build-server
RUN --mount=type=cache,target=/go/pkg/mod/ \
+ --mount=type=bind,target=. \
go build -o /bin/server ./cmd/server
FROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]
FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
```
## Summary
This section has shown how you can improve your build speed using cache and bind
mounts.
Related information:
- [Dockerfile reference](../../engine/reference/builder.md#run---mount)
- [Bind mounts](../../storage/bind-mounts.md)
## Next steps
The next section of this guide is an introduction to making your builds
configurable, using build arguments.
[Build arguments](build-args.md){: .button .primary-btn }

View File

@ -0,0 +1,274 @@
---
title: Multi-platform
description: Building for multiple operating systems and architectures
keywords: >-
build, buildkit, buildx, guide, tutorial, multi-platform, emulation,
cross-compilation
---
{% include_relative nav.html selected="8" %}
Up until this point in the guide, you've built Linux binaries. This section
describes how you can support other operating systems, and architectures, using
multi-platform builds via emulation and cross-compilation.
The easiest way to get started with building for multiple platforms is using
emulation. With emulation, you can build your app to multiple architectures
without having to make any changes to your Dockerfile. All you need to do is to
pass the `--platform` flag to the build command, specifying the OS and
architecture you want to build for.
The following command builds the server image for the `linux/arm/v7` platform:
```console
$ docker build --target=server --platform=linux/arm/v7 .
```
You can also use emulation to produce outputs for multiple platforms at once.
However, the default build driver doesn't support concurrent multi-platform
builds. So first, you need to switch to a different builder, that uses a driver
which supports concurrent multi-platform builds.
To switch to using a different driver, you're going to need to use the Docker
Buildx. Buildx is the next generation build client, and it provides a similar
user experience to the regular `docker build` command that youre used to, while
supporting additional features.
## Buildx setup
Buildx comes pre-bundled with Docker Desktop, and you can invoke this build
client using the `docker buildx` command. No need for any additional setup. If
you installed Docker Engine manually, you may need to install the Buildx plugin
separately. See
[Install Docker Buildx](https://docs.docker.com/build/buildx/install/) for
instructions.
Verify that the Buildx client is installed on your system, and that youre able
to run it:
```console
$ docker buildx version
github.com/docker/buildx v0.10.3 79e156beb11f697f06ac67fa1fb958e4762c0fab
```
Next, create a builder that uses the `docker-container`. Run the following
`docker buildx create` command:
```console
$ docker buildx create --driver=docker-container --name=container
```
This creates a new builder with the name `container`. You can list available
builders with `docker buildx ls`.
```console
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS
container docker-container
container_0 unix:///var/run/docker.sock inactive
default * docker
default default running
desktop-linux docker
desktop-linux desktop-linux running
```
The status for the new `container` builder is inactive. That's fine - it's
because you haven't started using it yet.
## Build using emulation
To run multi-platform builds with Buildx, invoke the `docker buildx build`
command, and pass it the same arguments as you did to the regular `docker build`
command before. Only this time, also add:
- `--builder=container` to select the new builder
- `--platform=linux/amd64,linux/arm/v7,linux/arm64/v8` to build for multiple
architectures at once
```console
$ docker buildx build \
--target=binaries \
--output=bin \
--builder=container \
--platform=linux/amd64,linux/arm64,linux/arm/v7 .
```
This command uses emulation to run the same build four times, once for each
platform. The build results are exported to a `bin` directory.
```text
bin
├── linux_amd64
│   ├── client
│   └── server
├── linux_arm64
│   ├── client
│   └── server
└── linux_arm_v7
├── client
└── server
```
When you build using a builder that supports multi-platform builds, the builder
runs all of the build steps under emulation for each platform that you specify.
Effectively forking the build into two concurrent processes.
![Build pipelines using emulation](./images/emulation.png){:.invertible}
There are, however, a few downsides to running multi-platform builds using
emulation:
- If you tried running the command above, you may have noticed that it took a
long time to finish. Emulation can be much slower than native execution for
CPU-intensive tasks.
- Emulation only works when the architecture is supported by the base image
youre using. The example in this guide uses the Alpine Linux version of the
`golang` image, which means you can only build Linux images this way, for a
limited set of CPU architectures, without having to change the base image.
As an alternative to emulation, the next step explores cross-compilation.
Cross-compiling makes multi-platform builds much faster and versatile.
## Build using cross-compilation
Using cross-compilation means leveraging the capabilities of a compiler to build
for multiple platforms, without the need for emulation.
The first thing you'll need to do is pinning the builder to use the nodes
native architecture as the build platform. This is to prevent emulation. Then,
from the node's native architecture, the builder cross-compiles the application
to a number of other target platforms.
### Platform build arguments
This approach involves using a few pre-defined build arguments that you have
access to in your Docker builds: `BUILDPLATFORM` and `TARGETPLATFORM` (and
derivatives, like `TARGETOS`). These build arguments reflect the values you pass
to the `--platform` flag.
For example, if you invoke a build with `--platform=linux/amd64`, then the build
arguments resolve to:
- `TARGETPLATFORM=linux/amd64`
- `TARGETOS=linux`
- `TARGETARCH=amd64`
When you pass more than one value to the platform flag, build stages that use
the pre-defined platform arguments are forked automatically for each platform.
This is in contrast to builds running under emulation, where the entire build
pipeline runs per platform.
![Build pipelines using cross-compilation](./images/cross-compilation.png){:.invertible}
### Update the Dockerfile
To build the app using the cross-compilation technique, update the Dockerfile as
follows:
- Add `--platform=$BUILDPLATFORM` to the `FROM` instruction for the initial
`base` stage, pinning the platform of the `golang` image to match the
architecture of the host machine.
- Add `ARG` instructions for the Go compilation stages, making the `TARGETOS`
and `TARGETARCH` build arguments available to the commands in this stage.
- Set the `GOOS` and `GOARCH` environment variables to the values of `TARGETOS`
and `TARGETARCH`. The Go compiler uses these variables to do
cross-compilation.
```diff
# syntax=docker/dockerfile:1
ARG GO_VERSION={{site.example_go_version}}
ARG GOLANGCI_LINT_VERSION={{site.example_golangci_lint_version}}
- FROM golang:${GO_VERSION}-alpine AS base
+ FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS base
WORKDIR /src
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=bind,source=go.mod,target=go.mod \
--mount=type=bind,source=go.sum,target=go.sum \
go mod download -x
FROM base AS build-client
+ ARG TARGETOS
+ ARG TARGETARCH
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=bind,target=. \
- go build -o /bin/client ./cmd/client
+ GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /bin/client ./cmd/client
FROM base AS build-server
+ ARG TARGETOS
+ ARG TARGETARCH
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=bind,target=. \
- go build -o /bin/server ./cmd/server
+ GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /bin/server ./cmd/server
FROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]
FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
FROM scratch AS binaries
COPY --from=build-client /bin/client /
COPY --from=build-server /bin/server /
FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION} as lint
WORKDIR /test
RUN --mount=type=bind,target=. \
golangci-lint run
```
The only thing left to do now is to run the actual build. To run a
multi-platform build, set the `--platform` option, and specify a CSV string of
the OS and architectures that you want to build for. The following command
illustrates how to build, and export, binaries for Mac (ARM64), Windows, and
Linux:
```console
$ docker buildx build \
--target=binaries \
--output=bin \
--builder=container \
--platform=darwin/arm64,windows/amd64,linux/amd64 .
```
When the build finishes, youll find client and server binaries for all of the
selected platforms in the `bin` directory:
```diff
bin
├── darwin_arm64
│   ├── client
│   └── server
├── linux_amd64
│   ├── client
│   └── server
└── windows_amd64
├── client
└── server
```
## Summary
This section has demonstrated how you can get started with multi-platform builds
using emulation and cross-compilation.
Related information:
- [Multi-platfom images](../building/multi-platform.md)
- [Drivers overview](../drivers/index.md)
- [Docker container driver](../drivers/docker-container.md)
- [`docker buildx create` CLI reference](../../engine/reference/commandline/buildx_create.md)
You may also want to consider checking out
[xx - Dockerfile cross-compilation helpers](https://github.com/tonistiigi/xx){: target="_blank" rel="noopener" }.
`xx` is a Docker image containing utility scripts that make cross-compiling with Docker builds easier.
## Next steps
This section is the final part of the Build with Docker guide. The following
page contains some pointers for where to go next.
[Next steps](next-steps.md){: .button .primary-btn }

193
build/guide/multi-stage.md Normal file
View File

@ -0,0 +1,193 @@
---
title: Multi-stage
description: Faster and smaller builds with multi-stage builds
keywords: build, buildkit, buildx, guide, tutorial, multi-stage
---
{% include_relative nav.html selected="3" %}
This section explores multi-stage builds. There are two main reasons for why
youd want to use multi-stage builds:
- They allow you to run build steps in parallel, making your build pipeline
faster and more efficient.
- They allow you to create a final image with a smaller footprint, containing
only what's needed to run your program.
In a Dockerfile, a build stage is represented by a `FROM` instruction. The
Dockerfile from the previous section doesnt leverage multi-stage builds. Its
all one build stage. That means that the final image is bloated with resources
used to compile the program.
```console
$ docker build --tag=buildme .
$ docker images buildme
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest c021c8a7051f 5 seconds ago 150MB
```
The program compiles to executable binaries, so you dont need to Go language
utilities to exist in the final image.
## Add stages
Using multi-stage builds, you can choose to use different base images for your
build and runtime environments. You can copy build artifacts from the build
stage over to the runtime stage.
Modify the Dockerfile as follows. This change creates another stage using a
minimal `scratch` image as a base. In the final `scratch` stage, the binaries
built in the previous stage are copied over to the filesystem of the new stage.
```diff
# syntax=docker/dockerfile:1
FROM golang:{{site.example_go_version}}-alpine
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
RUN go build -o /bin/client ./cmd/client
RUN go build -o /bin/server ./cmd/server
+
+ FROM scratch
+ COPY --from=0 /bin/client /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
```
Now if you build the image and inspect it, you should see a significantly
smaller number:
```console
$ docker build --tag=buildme .
$ docker images buildme
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme latest 436032454dd8 7 seconds ago 8.45MB
```
The image went from 150MB to only just 8.45MB in size. Thats because the
resulting image only contains the binaries, and nothing else.
## Parallelism
You've reduced the footprint of the image. The following step shows how you can
improve build speed with multi-stage builds, using parallelism. The build
currently produces the binaries one after the other. There is no reason why you
need to build the client before building the server, or vice versa.
You can split the binary-building steps into separate stages. In the final
`scratch` stage, copy the binaries from each corresponding build stage. By
segmenting these builds into separate stages, Docker can run them in parallel.
The stages for building each binary both require the Go compilation tools and
application dependencies. Define these common steps as a reusable base stage.
You can do that by assigning a name to the stage using the pattern
`FROM image AS stage_name`. This allows you to reference the stage name in a
`FROM` instruction of another stage (`FROM stage_name`).
You can also assign a name to the binary-building stages, and reference the
stage name in the `COPY --from=stage_name` instruction when copying the binaries
to the final `scratch` image.
```diff
# syntax=docker/dockerfile:1
- FROM golang:{{site.example_go_version}}-alpine
+ FROM golang:{{site.example_go_version}}-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
+
+ FROM base AS build-client
RUN go build -o /bin/client ./cmd/client
+
+ FROM base AS build-server
RUN go build -o /bin/server ./cmd/server
FROM scratch
- COPY --from=0 /bin/client /bin/server /bin/
+ COPY --from=build-client /bin/client /bin/
+ COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
```
Now, instead of first building the binaries one after the other, the
`build-client` and `build-server` stages are executed simultaneously.
![Stages executing in parallel](./images/parallelism.gif)
## Build targets
The final image is now small, and youre building it efficiently using
parallelism. But this image is slightly strange, in that it contains both the
client and the server binary in the same image. Shouldnt these be two different
images?
Its possible to create multiple different images using a single Dockerfile. You
can specify a target stage of a build using the `--target` flag. Replace the
unnamed `FROM scratch` stage with two separate stages named `client` and
`server`.
```diff
# syntax=docker/dockerfile:1
FROM golang:{{site.example_go_version}}-alpine AS base
WORKDIR /src
COPY go.mod go.sum .
RUN go mod download
COPY . .
FROM base AS build-client
RUN go build -o /bin/client ./cmd/client
FROM base AS build-server
RUN go build -o /bin/server ./cmd/server
- FROM scratch
- COPY --from=build-client /bin/client /bin/
- COPY --from=build-server /bin/server /bin/
- ENTRYPOINT [ "/bin/server" ]
+ FROM scratch AS client
+ COPY --from=build-client /bin/client /bin/
+ ENTRYPOINT [ "/bin/client" ]
+ FROM scratch AS server
+ COPY --from=build-server /bin/server /bin/
+ ENTRYPOINT [ "/bin/server" ]
```
And now you can build the client and server programs as separate Docker images
(tags):
```console
$ docker build --tag=buildme-client --target=client .
$ docker build --tag=buildme-server --target=server .
$ docker images buildme
REPOSITORY TAG IMAGE ID CREATED SIZE
buildme-client latest 659105f8e6d7 20 seconds ago 4.25MB
buildme-server latest 666d492d9f13 5 seconds ago 4.2MB
```
The images are now even smaller, about 4 MB each.
This change also avoids having to build both binaries each time. When selecting
to build the `client` target, Docker only builds the stages leading up to
that target. The `build-server` and `server` stages are skipped if theyre not
needed. Likewise, building the `server` target skips the `build-client` and
`client` stages.
## Summary
Multi-stage builds are useful for creating images with less bloat and a smaller
footprint, and also helps to make builds run faster.
Related information:
- [Multi-stage builds](../building/multi-stage.md)
- [Base images](../building/base-images.md)
## Next steps
The next section describes how you can use file mounts to further improve build
speeds.
[Mounts](mounts.md){: .button .primary-btn }

10
build/guide/nav.html Normal file
View File

@ -0,0 +1,10 @@
<ul class="pagination">
<li {% if include.selected=="1"%}class="active"{% endif %}><a href="/build/guide/intro/">Introduction</a></li>
<li {% if include.selected=="2"%}class="active"{% endif %}><a href="/build/guide/layers/">Layers</a></li>
<li {% if include.selected=="3"%}class="active"{% endif %}><a href="/build/guide/multi-stage/">Multi-stage</a></li>
<li {% if include.selected=="4"%}class="active"{% endif %}><a href="/build/guide/mounts/">Mounts</a></li>
<li {% if include.selected=="5"%}class="active"{% endif %}><a href="/build/guide/build-args/">Build arguments</a></li>
<li {% if include.selected=="6"%}class="active"{% endif %}><a href="/build/guide/export/">Export binaries</a></li>
<li {% if include.selected=="7"%}class="active"{% endif %}><a href="/build/guide/test/">Test</a></li>
<li {% if include.selected=="8"%}class="active"{% endif %}><a href="/build/guide/multi-platform/">Multi-platform</a></li>
</ul>

34
build/guide/next-steps.md Normal file
View File

@ -0,0 +1,34 @@
---
title: Next steps
description: Next steps following the Docker Build guide
keywords: build, buildkit, buildx, guide, tutorial
---
This guide has demonstrated some of the build features and capabilities
that Docker provides.
If you would like to continue learning about Docker build, consider exploring
the following resources:
- [BuildKit](../buildkit/index.md): deep-dive into the open source build engine
that powers your Docker builds
- [Drivers](../drivers/index.md): configure for how and where your Docker builds
run
- [Exporters](../exporters/index.md): save your build results to different
output formats
- [Bake](../bake/index.md): orchestrate your build workflows
- [Attestations](../attestations/index.md): annotate your build artifacts with
metadata
- [Continuous integration](../ci/index.md): run Docker builds in CI
## Feedback
If you have suggestions for improving the content of this guide, you can use the
feedback widget to submit your feedback.
If you don't see the feedback widget, try turning off your content filtering
extension or ad blocker, if you use one.
You can also submit an issue on
[the docs GitHub repository](https://github.com/docker/docs/issues/new){: target="_blank" rel="noopener" },
if you prefer.

117
build/guide/test.md Normal file
View File

@ -0,0 +1,117 @@
---
title: Test
description: Running tests with Docker Build
keywords: build, buildkit, buildx, guide, tutorial, testing
---
{% include_relative nav.html selected="7" %}
This section focuses on testing. The example in this section focuses on linting,
but the same principles apply for other kinds of tests as well, such as unit
tests. Code linting is a static analysis of code that helps you detect errors,
style violations, and anti-patterns.
The exact steps for how to test your code can vary a lot depending on the
programming language or framework that you use. The example application used in
this guide is written in Go. You will add a build step that uses
`golangci-lint`, a popular linters runner for Go.
## Run tests
The `golangci-lint` tool is available as an image on Docker Hub. Before you add
the lint step to the Dockerfile, you can try it out using a `docker run`
command.
```console
$ docker run -v $PWD:/test -w /test \
golangci/golangci-lint golangci-lint run
```
You will notice that `golangci-lint` works: it finds an issue in the code where
there's a missing error check.
```text
cmd/server/main.go:23:10: Error return value of `w.Write` is not checked (errcheck)
w.Write([]byte(translated))
^
```
Now you can add this as a step to the Dockerfile.
```diff
# syntax=docker/dockerfile:1
ARG GO_VERSION={{site.example_go_version}}
+ ARG GOLANGCI_LINT_VERSION={{site.example_golangci_lint_version}}
FROM golang:${GO_VERSION}-alpine AS base
WORKDIR /src
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,source=go.sum,target=go.sum \
--mount=type=bind,source=go.mod,target=go.mod \
go mod download -x
FROM base AS build-client
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,target=. \
go build -o /bin/client ./cmd/client
FROM base AS build-server
ARG APP_VERSION="0.0.0+unknown"
RUN --mount=type=cache,target=/go/pkg/mod/ \
--mount=type=bind,target=. \
go build -ldflags "-X main.version=$APP_VERSION" -o /bin/server ./cmd/server
FROM scratch AS client
COPY --from=build-client /bin/client /bin/
ENTRYPOINT [ "/bin/client" ]
FROM scratch AS server
COPY --from=build-server /bin/server /bin/
ENTRYPOINT [ "/bin/server" ]
FROM scratch AS binaries
COPY --from=build-client /bin/client /
COPY --from=build-server /bin/server /
+
+ FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION} as lint
+ WORKDIR /test
+ RUN --mount=type=bind,target=. \
+ golangci-lint run
```
The added `lint` stage uses the `golangci/golangci-lint` image from Docker Hub
to invoke the `golangci-lint run` command with a bind-mount for the build
context.
The lint stage is independent of any of the other stages in the Dockerfile.
Therefore, running a regular build wont cause the lint step to run. To lint the
code, you must specify the `lint` stage:
```console
$ docker build --target=lint .
```
## Export test results
In addition to running tests, it's sometimes useful to be able to export the
results of a test to a test report.
Exporting test results is no different to exporting binaries, as shown in the
previous section of this guide:
1. Save the test results to a file.
2. Create a new stage in your Dockerfile using the `scratch` base image.
3. Export that stage using the `local` exporter.
The exact steps on how to do this is left as a reader's exercise :-)
## Summary
This section has shown an example on how you can use Docker builds to run tests
(or as shown in this section, linters).
## Next steps
The next topic in this guide is multi-platform builds, using emulation and
cross-compilation.
[Multi-platform](multi-platform.md){: .button .primary-btn }