概要
在参照使用 Ubuntu 22.04使用Podman部署OpenGrok的详细教程 进行 OpenGrok 部署的时候,其中的 Dockerfile 配置了两个独立的镜像来源。恰好这两个镜像来源都需要配置 PIP 国内镜像。于是思考如何在同一个 Dockerfile 存在两个镜像的场景下,如果共用一个变量。
研究了一阵,发现还是通过 ARG 变量实现上述想法。
具体说明参考下面的完整文章。
Introduction
The Docker ecosystem is rich with tools and best practices that streamline containerization. One of these practices is using multistage builds to create lean, efficient containers. However, as your Dockerfiles grow more complex, managing variables and maintaining readability can become a challenge. Enter the ARG instruction—your key to sharing variables across stages in a multistage Dockerfile. In this blog post, we’ll explore how ARG can simplify your Dockerfiles, enhance reusability, and maintain cleaner code.
Why Use Multistage Builds?
Multistage builds in Docker allow you to use multiple FROM statements in a single Dockerfile, creating separate stages that can be used to build a final image. This method is particularly useful for creating lightweight production images, as you can copy only the necessary artifacts from earlier stages. Here’s a quick example to illustrate:
|
# Stage 1: Build stage FROM golang:1.16 AS builder WORKDIR /app COPY . . RUN go build -o myapp . # Stage 2: Production stage FROM alpine:latest COPY --from=builder /app/myapp /usr/local/bin/myapp CMD ["myapp"] |
In this simple example, the build stage compiles a Go application, and the production stage creates a minimal image containing only the compiled binary.
The Challenge: Sharing Variables Across Stages
While multistage builds are powerful, they can introduce a common challenge: variable reuse. Suppose you need to use a specific version of an application or a common path across multiple stages. Without a way to share variables, you might end up duplicating code, leading to maintenance headaches and potential errors.
Here’s where ARG (argument) comes into play. The ARG instruction allows you to define variables that can be used throughout your Dockerfile, even across different stages.
Introducing ARG: The Basics
The ARG instruction defines a variable that users can pass at build time to customize the build process. Unlike environment variables (ENV), which are persisted in the image, ARG variables are only available during the build process and do not become part of the final image.
Let’s start with a basic example:
|
# Define the argument with a default value ARG BASE_IMAGE=alpine:3.12 # Use the argument in the FROM instruction FROM ${BASE_IMAGE} RUN echo "This image is based on ${BASE_IMAGE}" |
In this example, BASE_IMAGE is an argument that can be overridden when building the Dockerfile. The default value is alpine:3.12, but you could specify a different base image at build time:
|
docker build --build-arg BASE_IMAGE=ubuntu:20.04 -t custom-image . |
Sharing ARG Variables Across Stages
The real power of ARG comes into play with multistage builds. To share ARG variables between stages, you need to redefine the ARG in each stage. Let’s look at a more advanced example:
|
# Define an argument for the Go version ARG GO_VERSION=1.16 # Stage 1: Build stage FROM golang:${GO_VERSION} AS builder ARG GO_VERSION WORKDIR /app COPY . . RUN go build -o myapp . # Stage 2: Production stage FROM alpine:latest ARG GO_VERSION RUN echo "Built with Go version ${GO_VERSION}" COPY --from=builder /app/myapp /usr/local/bin/myapp CMD ["myapp"] |
In this Dockerfile, we define GO_VERSION as an argument at the top. By repeating ARG GO_VERSION in each stage, we make the argument available for use. Notice how the build stage uses GO_VERSION to specify the Go image, and the production stage echoes the Go version used.
Advanced Usage: Combining ARG with Environment Variables
You might find it useful to combine ARG with ENV to set environment variables conditionally based on build arguments. This can further enhance your Dockerfile’s flexibility.
Following example demonstrates the use of ARG and ENV to have a generic Dockerfile for a Turborepo application:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
|
ARG APP_NAME="web" ARG PNPM_HOME="/root/.local/share/pnpm" FROM node:20-alpine AS base FROM base AS builder # Set working directory WORKDIR /app ARG APP_NAME ARG PNPM_HOME ENV PNPM_HOME=${PNPM_HOME} ENV PATH="${PATH}:${PNPM_HOME}" RUN corepack enable RUN pnpm add -g turbo@2.0.4 COPY . . # Collect all the necessary dependencies for the project RUN turbo prune ${APP_NAME} --docker # Add lockfile and package.json's of isolated subworkspace FROM base AS installer WORKDIR /app ARG APP_NAME ARG PNPM_HOME ENV PNPM_HOME=${PNPM_HOME} ENV PATH="${PATH}:${PNPM_HOME}" RUN corepack enable # First install dependencies (as they change less often) COPY .gitignore .gitignore COPY --from=builder /app/out/json/ . COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml RUN pnpm install # Build the project and its dependencies COPY --from=builder /app/out/full/ . COPY turbo.json turbo.json # Build the app RUN pnpm turbo build --filter=${APP_NAME} FROM base AS production WORKDIR /app ARG APP_NAME ARG PNPM_HOME ENV PNPM_HOME=${PNPM_HOME} ENV PATH="${PATH}:${PNPM_HOME}" ENV NODE_ENV="production" RUN corepack enable COPY --from=installer /app . USER node WORKDIR /app/apps/${APP_NAME} CMD pnpm start |
Key Points on Environment Variables in Multistage Builds
Debugging ARG Variables
When working with ARG variables, you might run into issues where arguments aren’t passed correctly or variables aren’t set as expected. Here are some tips to help you debug:
-
Check Build Logs: Use docker build with the --progress=plain flag to get more detailed logs that can help identify where arguments are being used or missed.
|
docker build --progress=plain -t debug-image . |
-
Echo Variables: Add RUN echo statements to print the values of your ARG variables during the build process.
|
RUN echo "ARG BASE_IMAGE=${BASE_IMAGE}" |
-
Use Default Values: Define sensible default values for your ARG variables to ensure that your build doesn’t fail if arguments are not provided.
Best Practices for Using ARG
- Define ARG Variables Early: Place ARG instructions at the top of your Dockerfile to make them accessible in all stages.
- Use Descriptive Names: Choose meaningful names for your arguments to make the Dockerfile easier to understand and maintain.
- Avoid Secrets in ARG: Never use ARG to pass sensitive data like passwords or API keys, as they can be exposed in the Docker image history.
Conclusion
Using ARG to share variables across stages in a multistage Dockerfile can significantly improve your Docker builds’ maintainability and flexibility. Whether you’re building lightweight production images or dynamically configuring your builds, ARG provides a powerful tool to streamline and enhance your Dockerfile.
参考链接