ARG Directives: Enhancing Flexibility and Reducing Code Duplication
Docker is a powerful tool for containerization and deployment of applications. When creating Docker images, it's common to encounter scenarios where certain values need to be dynamic or configurable.
In this article, we'll explore how to improve the Dockerfile by leveraging ARG directives to make the Alpine version dynamic and avoid code duplication.
Let's start with the initial Dockerfile:
FROM alpine:3.18 as base
RUN echo "Alpine version 3.18"
RUN echo "Alpine version 3.18" > message.txt
CMD cat message.txt
FROM base as production
RUN echo "Alpine version 3.18"
RUN echo "Alpine version 3.18" > message.txt
CMD cat message.txt
build and run
$> docker build -t base --target base .
[base 2/3] RUN echo "Alpine version 3.18"
[base 3/3] RUN echo "Apline version 3.18" > message.txt
$> docker run base
Apline version 3.18
If we do similar build an run with target production then
$> docker build -t production --target production .
[production 1/2] RUN echo "Alpine version 3.18"
[production 2/2] RUN echo "Apline version 3.18" > message.txt
$> docker run production
Apline version 3.18
In the above Dockerfile, the Alpine version is hard-coded as 3.18, which leads to code duplication and lack of flexibility. We can improve this step by step using ARG directives.
Step 1: Dynamic Alpine Version in the FROM Directive
We can use the ARG directive to define an argument that can be expanded during the build process. Let's modify the first two lines as follows:
ARG ALPINE_VERSION
FROM alpine:${ALPINE_VERSION} as base
Now, during the Docker build command, we can pass the value for the ALPINE_VERSION argument using the --build-arg flag:
docker build -t base --target base --build-arg ALPINE_VERSION=3.18 .
This allows us to have a more flexible image where the Alpine version can be configured at build time.
Step 2: Default Value for Dynamic Alpine Version
We can provide a default value for the ALPINE_VERSION argument by modifying the first line as follows:
ARG ALPINE_VERSION='3.18'
Now, if the ALPINE_VERSION argument is not provided during the Docker build command, the default value will be used:
docker build -t base --target base .
docker run base
Apline version 3.18
Notice that the value 3.18 is used even when was not provided in the docker build command.
$> docker build -t base --target base --build-arg ALPINE_VERSION=3.17 .
[internal] load metadata for docker.io/library/alpine:3.17
Our build process use the value provided in the docker build command using the --build-arg flag
Step 3: Using the Same Value Inside a Build Stage
In the original Dockerfile, the ARG ALPINE_VERSION was not expanded inside the build stage. To make it accessible, we need to duplicate its definition inside the build stage. Modify the Dockerfile as follows:
ARG ALPINE_VERSION='3.18'
FROM alpine:${ALPINE_VERSION} as base
ARG ALPINE_VERSION
RUN echo "Alpine version ${ALPINE_VERSION}"
RUN echo "Alpine version ${ALPINE_VERSION}" > message.txt
Now, the ARG ALPINE_VERSION is accessible inside the build stage, and the default value is also available:
$> docker build -t base --target base .
[base 2/3] RUN echo "Alpine version 3.18"
[base 3/3] RUN echo "Apline version 3.18" > message.txt
$> docker run base
Apline version 3.18
IMPORTANT: The line ARG ALPINE_VERSION . The ARG must be re-defined(duplicated) inside the build stage
Step 4: Using the Same Value Inside Different Build Stages
When Inherits
If build stages inherit from each other, they can access the arguments defined in their parent stage. Modify the Dockerfile as follows:
ARG ALPINE_VERSION='3.18'
FROM alpine:${ALPINE_VERSION} as base
ARG ALPINE_VERSION
RUN echo "Alpine version ${ALPINE_VERSION}"
RUN echo "Alpine version ${ALPINE_VERSION}" > message.txt
CMD cat message.txt
FROM base as production
RUN echo "Alpine version ${ALPINE_VERSION}"
RUN echo "Alpine version ${ALPINE_VERSION}" > message.txt
CMD cat message.txt
In this case, the build stage "production" inherits from "base," so it can access the arguments defined in the "base" stage without duplicating their definitions. The same value of ALPINE_VERSION will be used in both stages.
$> docker build -t production --target production .
=> CACHED [base 2/3] RUN echo "Alpine version 3.18"
=> CACHED [base 3/3] RUN echo "Apline version 3.18" > message.txt
=> [production 1/2] RUN echo "Alpine version 3.18"
=> [production 2/2] RUN echo "Apline version 3.18" > message.txt
$> docker run production
Apline version 3.18
When no Inherits
If a build stage does not inherit from another stage, the arguments need to be duplicated inside that stage for access.
ARG ALPINE_VERSION='3.18'
FROM alpine:${ALPINE_VERSION} as base
ARG ALPINE_VERSION
RUN echo "Alpine version ${ALPINE_VERSION}"
RUN echo "Apline version ${ALPINE_VERSION}" > message.txt
CMD cat message.txt
FROM alpine:${ALPINE_VERSION} as staging
ARG ALPINE_VERSION
RUN echo "Alpine version ${ALPINE_VERSION}"
RUN echo "Apline version ${ALPINE_VERSION}" > message.txt
CMD cat message.txt
ouput:
$> docker build -t staging --target staging .
=>[staging 3/3] RUN echo "Apline version 3.18"
> message.txt
$> docker run staging
Apline version 3.18
Notice: re-duplicate ARG ALPINE_VERSION
Summary
By using ARG directives, we can avoid code duplication and build more flexible Docker images.
- ARGs defined outside build stages (before FROM) are accessible only in the lines with the FROM directive.
- ARGs defined inside a build stage are accessible only within that stage and its child stages.
- To access the value of an ARG defined outside from inside a build stage, duplicate its definition in the build stage.
Top comments (0)