Enhancing Dockerfile Flexibility: The Power of ARG Directives
Tags: docker
Published: 2023-11-03 03:40:14

### 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.