mirror of https://github.com/docker/docs.git
build: added guide for docker build
Signed-off-by: David Karlsson <david.karlsson@docker.com>
This commit is contained in:
parent
178972a3d5
commit
da6586c498
|
@ -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
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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
|
||||
that’s 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 }
|
|
@ -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 don’t 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`. It’s created under the
|
||||
`bin/` directory because that’s 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 don’t 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 |
|
@ -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 you’re just getting started, or you’re 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 don’t 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 }
|
|
@ -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.
|
||||
|
||||
Here’s 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" ]
|
||||
```
|
||||
|
||||
Here’s 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 }
|
|
@ -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.
|
||||
|
||||
{:.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.
|
||||
|
||||
{:.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.
|
||||
|
||||
{:.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 }
|
|
@ -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 you’re
|
||||
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 it’s 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 weren’t 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, you’ll 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 container’s 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 }
|
|
@ -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 you’re 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 you’re 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.
|
||||
|
||||
{:.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
|
||||
you’re 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 node’s
|
||||
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.
|
||||
|
||||
{:.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, you’ll 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 }
|
|
@ -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
|
||||
you’d 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 doesn’t leverage multi-stage builds. It’s
|
||||
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 don’t 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. That’s 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.
|
||||
|
||||

|
||||
|
||||
## Build targets
|
||||
|
||||
The final image is now small, and you’re 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. Shouldn’t these be two different
|
||||
images?
|
||||
|
||||
It’s 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 they’re 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 }
|
|
@ -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>
|
|
@ -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.
|
|
@ -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 won’t 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 }
|
Loading…
Reference in New Issue