2.5. Benefits of custom bootable images with multi-stage builds
The deployment image must include only the application and its required runtime, without adding any build tools or unnecessary libraries. To achieve this, use a two-stage Containerfile: one stage for building the artifacts and another for hosting the application.
With multi-stage builds, you use multiple FROM instructions in your Containerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, and exclude everything you do not need in the final image.
Multi-stage builds offer several advantages:
- Smaller image size
- By separating the build environment from the runtime environment, only the necessary files and dependencies are included in the final image, significantly reducing its size.
- Improved security
- Since build tools and unnecessary libraries are excluded from the final image, the attack surface is reduced, leading to a more secure container.
- Optimized performance
- A smaller image size means faster download, deployment, and startup times, improving the overall efficiency of the containerized application.
- Simplified maintenance
- With the build and runtime environments separated, the final image is cleaner and easier to maintain, containing only what is needed to run the application.
- Cleaner builds
- Multi-stage builds help avoid clutter from intermediate files, which could accumulate during the build process, ensuring that only essential artifacts make it into the final image.
- Resource efficiency
- The ability to build in one stage and discard unnecessary parts minimizes the use of storage and bandwidth during deployment.
- Better layer caching
- With clearly defined stages, Podman can efficiently cache the results of previous stages, potentially speeding up future builds.
The following Containerfile consists of two stages. The first stage is typically named builder and it compiles a golang binary. The second stage copies the binary from the first stage. The default working directory for the go-toolset builder is opt/ap-root/src.
FROM registry.access.redhat.com/ubi10/go-toolset:latest as builder
RUN echo 'package main; import "fmt"; func main() { fmt.Println("hello world") }' > helloworld.go
RUN go build helloworld.go
FROM registry.redhat.io/rhel10/rhel-bootc:latest
COPY --from=builder /opt/app-root/src/helloworld /
CMD ["/helloworld"]
As a result, the final container image includes the helloworld binary but no data from the previous stage.
You can also use multi-stage builds to perform the following scenarios:
- Stopping at a specific build stage
- When you build your image, you can stop at a specified build stage. For example:
$ podman build --target build -t hello .
For example, you can use this approach to debugging a specific build stage.
- Using an external image as a stage
-
You can use the
COPY --frominstruction to copy from a separate image either using the local image name, a tag available locally or on a container registry, or a tag ID. For example:
COPY --from=<image> <source_path> <destination_path>
- Using a previous stage as a new stage
-
You can continue where a previous stage ended by using the
FROMinstruction. From example:
FROM ubi10 AS stage1
[...]
FROM stage1 AS stage2
[...]
FROM ubi10 AS final-stage
[...]