Introduction

In a recent project, I encountered the need to dynamically load different base images based on the Node.js version I wanted for testing. Docker came to the rescue, offering a simple yet powerful solution using command-line arguments within the FROM section of a Dockerfile.

The Dockerfile

Let’s start with a straightforward example of a Dockerfile with the dynamic version loading included.

ARG NODE_VERSION_TAG
FROM node:${NODE_VERSION_TAG} as build

The ARG keyword sets up a build-time variable named NODE_VERSION_TAG. In the FROM clause, we immediately utilize this variable with ${NODE_VERSION_TAG}, dynamically loading the specified version/tag of the node image.

Building with dynamic arguments

When you execute the build process you can specify an argument as follows:

docker build --build-arg NODE_VERSION_TAG=latest -t custom-image-name:v1.0 .

You can also choose any other tag associated with different Node.js versions and underlying OS combinations.

docker build --build-arg NODE_VERSION_TAG=21.2.0 -t custom-image-name:v1.0 .

The container would then be built using the node image at version 21.2.0 as its base.

Extending for image name and tag

To take it a step further, you can dynamically alter both the image name and tag.

ARG BASE_IMAGE_NAME
ARG BASE_IMAGE_TAG
FROM ${BASE_IMAGE_NAME}:${BASE_IMAGE_TAG} as build

Now, you can build with different base images.

docker build --build-arg BASE_IMAGE_NAME=node --build-arg BASE_IMAGE_TAG=21.2.0 -t custom-image-name-node:v1.0 .
docker build --build-arg BASE_IMAGE_NAME=rust --build-arg BASE_IMAGE_TAG=1.74.0 -t custom-image-name-rust:v1.0 .

This approach extends the versatility of your Dockerfile, allowing you to seamlessly adapt to various base images and tags.

Github Actions workflow

As a bonus here is a Github Actions workflow that uses this technique to build a docker image against multiple versions of node.js.

name: Node.js Version Matrix

on:
  push:
    branches:
      - main

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [14, 16, 18]

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v2

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - name: Build and Test
        run: |
          docker build --build-arg NODE_VERSION_TAG=${{ matrix.node-version }} -t myapp-${{ matrix.node-version }}:test .
          docker run myapp-${{ matrix.node-version }}:test npm test          

This workflow is performing the following actions.

  • The workflow triggers on pushes to the main branch.
  • It defines a job named “test” that runs on the latest version of Ubuntu.
  • The build matrix is configured with three different Node.js versions: 14, 16, and 18.
  • The actions/checkout action is used to fetch the repository.
  • The actions/setup-node action is used to set up the specified Node.js version from the matrix.
  • The build and test steps use the docker build command with the NODE_VERSION_TAG argument set to the corresponding Node.js version from the matrix.
  • The docker run command executes your project’s tests inside the Docker container.

With this workflow, your project will be built and tested against multiple Node.js versions, providing you with valuable insights into its compatibility across different environments.