How to create a Generic Docker Base Image with Monorepo and Semantic versioning? Part 2/3
This is the second part of "How to create a Generic Docker Base Image with Monorepo and Semantic versioning?". If you haven't read the previous post yet, please check out the Part1 before continue.
In this article, we will create the necessary configurations to set up the Docker Base Image, Github Actions and Docker Hub repository.
Dockerfile
We are going to create a Generic Docker Base Image from node:18.20.3-slim. It is the official image of Node v18. So, let's add the following content to the packages/node-18/Dockerfile file:
# base image
FROM node:18.20.3-slim
# add here whatever you need to make this docker base image unique for you
To avoid send unnecessary files to the docker build context, is a good practice ignore all files by default and only include those files that your Docker Image needs. We will add the following content to the packages/node-18/.dockerignore file:
# ignore all by default
*
# exclusions
If you need to include special files, just prepend lines with a ! (exclamation mark) to make exceptions to exclusions. Ex: !README.md
NX target Defaults
Nx has a feature called target defaults which provides ways to set common options for a particular target in our workspace. For our case, we will create a build target option in nx.json file with the following content:
{
"extends": "nx/presets/npm.json",
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"targetDefaults": {
"build": {
"executor": "nx:run-commands",
"options": {
"projectName": "my-docker-nodejs-base-image",
"dockerRepository": "your docker repository name",
"commands": [
"echo \"docker_context=./{args.projectSourceDir}\" >> $GITHUB_ENV",
"echo \"docker_file=./{args.projectSourceDir}/Dockerfile\" >> $GITHUB_ENV",
"echo \"docker_tag={args.dockerRepository}/{args.projectName}:{args.nodeImage}-{args.projectVersion}\" >> $GITHUB_ENV"
],
"forwardAllArgs": false
}
}
}
}
Now, let's explain the relevant sections:
build: As the default target sets common options for particular targets, we need to define the "build" target name. By defining the default target we will provide the same executor and options for all packages.
options:
projectName: We will use this option to set the docker base image tag.
dockerRepository: The repository name of your docker hub account.
commands: As we will build the docker image using GitHubActions we need to set some environment variables that we will use on the "docker/build-push-action@v6" steps:
echo "docker_context=./{args.projectSourceDir}" >> $GITHUB_ENV
echo "docker_file=./{args.projectSourceDir}/Dockerfile" >> $GITHUB_ENV
echo "docker_tag={args.dockerRepository}/{args.projectName}:{args.nodeImage}-{args.projectVersion}" >> $GITHUB_ENV
The nx command line send the arguments: projectSourceDir, dockerRepository, projectVersion and projectName. And we will use these arguments to set the docker_context path, docker_file path and docker_tag variable with the docker image tag name.
NX Command line
As we have already defined the build target option, we can test it by running this command:
npx nx build node-18 --args="--projectVersion={project.version} --nodeImage={project.nodeImage} --projectSourceDir={project.sourceRoot}" --verbose
You should get something like that:
> nx run node-18:build --args=--projectVersion={project.version} --nodeImage={project.nodeImage} --projectSourceDir={project.sourceRoot}
> echo "docker_context=./packages/node-18" >> $GITHUB_ENV
> echo "docker_file=./packages/node-18/Dockerfile" >> $GITHUB_ENV
> echo "docker_tag=maxmartinezc/my-docker-nodejs-base-image:18.20.3-1.0.0" >> $GITHUB_ENV
/bin/sh: $GITHUB_ENV: ambiguous redirect
/bin/sh: $GITHUB_ENV: ambiguous redirect
/bin/sh: $GITHUB_ENV: ambiguous redirect
Warning: command "echo "docker_context=./packages/node-18" >> $GITHUB_ENV" exited with non-zero status code
Warning: command "echo "docker_file=./packages/node-18/Dockerfile" >> $GITHUB_ENV" exited with non-zero status code
Warning: command "echo "docker_tag=your_docker_repository_name/my-docker-nodejs-base-image:18.20.3-1.0.0" >> $GITHUB_ENV" exited with non-zero status code
———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
We can noticed that the monorepo works because the console shows the base image tag with the node version and the project semantic version: "your_docker_repository_name/my-docker-nodejs-base-image:18.20.3-1.0.0"
Ignore the fact that the command return errors or warning. Doesn't matter in this context, because this command was made to run on GitHub and not in a local environment.
DockerHub
Before jump into the DockerHub Repository, you have to have a Docker Hub account. if you don't have one, you can create an account here.
Create the DockerHub repository
Go to the repository page and click the "Create repository" button:
After that, type the repository name and make it public or private (this option is up to you. For this article, I selected public)
Important: Don't forget update the dockerRepository option in nx.json file:
{
...
"targetDefaults": {
"build": {
...
"options": {
...
"dockerRepository": "put your docker repository name",
...
}
}
}
}
DockerHub token
To allow the GitHub action to push the docker image, we need to have a "Personal Access Token". So, go to your Account settings>Security>Personal access tokens. Add a description and Select "Read & Write" access permissions:
Don't forget copy the token value after click the "Generate" button. We will use it in the next section to setup the GitHub Actions secrets.
Github Actions
For this section, you should have GitHub repository. If you don't have one, you are not able to continue whit this section (But you can create one...)
GitHub Secrets
Once you have your GitHub repository we can go ahead with the GitHub Secrets setup. For that, go to the repository Settings>Security>Secrets and variables>Actions.
We are going to create 2 Repository secret:
Create one with name DOCKER_PASSWORD and value "your personal access token" that you created in the previous section
Create another one with name DOCKER_USERNAME and value "your docker hub account name"
You should have something like this:
Github Workflow
Create the ".github" folder with a sub folder named "workflows". Then, create a new file named "ci.yml" in ".github/workflows" and add the following content:
name: CI
on:
push:
branches:
- master
jobs:
main:
runs-on: ubuntu-latest
permissions:
contents: read
actions: read
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: "18"
- run: npm ci
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
Basically the first part of the pipeline define the necessary steps to checkout the code, setup node and pass the docker secrets to docker login action.
Affected Packages Step
The second part of the pipeline is "Affected Package" step:
- name: Affected Package
id: affected_package
uses: nrwl/nx-set-shas@v4
with:
main-branch-name: "master"
- run: |
npx nx affected --target=build --parallel=1 --args="--projectVersion={project.version} --nodeImage={project.nodeImage} --projectSourceDir={project.sourceRoot}"
Nx affected step
Nx provides the nx affected command to analysis and determine the minimum set of projects that were affected by the change and only run the build task of the affected projects:
npx nx affected --target=build --parallel=1 --args="--projectVersion={project.version} --nodeImage={project.nodeImage} --projectSourceDir={project.sourceRoot}"
As we said early, with this command line we send the necessary arguments to set the $GITHUB_ENV variables.
Also, we need the "nrwl/nx-set-shas@v4" Github Action to set the base and head SHAs required for the nx affected commands in CI.
Build and push Docker Image step
The last part of the pipeline is the "Build and push Docker Image" step:
- name: Build and push Docker Image
if: ${{ env.docker_file != '' }}
uses: docker/build-push-action@v6
with:
context: ${{ env.docker_context }}
file: ${{ env.docker_file }}
push: true
tags: ${{ env.docker_tag }}
We are going to use docker/build-push-action@v6 action to build and push the docker image. As we defined early, the GITHUB_ENV variable has been set with docker_context, docker_file and docker_tag to be used as input of this step.
Important: By using the if condition we will only run this steps when the env.docker_file is defined.
Running the pipeline
Finally, we can commit and push the changes into GitHub repository and have a look at the "Actions" options. If everything gone well, you will see the first run execution with a successful status:
Also, go to your docker docker hub repository. You should have a new tag like this:
Now that you’ve read the second part, you know exactly how to configure a package of node-18, set up the GitHub Pipeline & Docker Hub repository.
Ready to read the last part? Subscribe to my newsletter and don't miss the next article to complete this serie. See you then.
Subscribe to my newsletter
Read articles from Max Martínez Cartagena directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Max Martínez Cartagena
Max Martínez Cartagena
I'm an enthusiastic Chilean software engineer in New Zeland. I mostly focus on the back-end of the systems. This is my site, Señor Developer, where I share my knowledge and experience.