Skip to main content

Build Docker image of a NodeJS app

Background on Continuous Integration

Continuous Integration is automated builds that can be triggered by some sort of event, such as a code check-in, merge, or on a regular schedule. The end goal of a build is to be deployed somewhere, and the main goal of Continuous Integration is to build and publish that deployable unit.

However, more than the compiled source code can go into a build. The end product for Continuous Integration is a release candidate. A release candidate is the final form of an artifact to be deployed. There could be quality steps taken to produce the artifact, such as finding bugs, and identifying their fix. Packaging, distribution, and configuration all go into a release candidate.

According to Paul Duvall, co-author of Continuous Integration, in a nutshell, CI will improve quality and reduce risk. Having a Continuous Integration approach frees teams from the burden of manual builds and also makes builds more repeatable, consistent, and available. If you are unfamiliar with CI, this guide will get you started on your first automated build.

tip

If you don't have a Harness account yet, you can create one for free at app.harness.io

Your Local Build - Onramp to Continuous Integration

To create a build, you need to have something that can be built, which means source code. The steps you take to build and package your application or service need to be represented in a CI tool or platform for automation. CI platforms will need to connect to source code management e.g SCM to start the build process. This can be as simple as connecting your public GitHub Repository for something that needs to be built.

Local Build Overview

How to Build an App Locally?

Languages and package formats have build specific tools. As an example, here is a simplistic NodeJS Application that can be built into a Docker Image; the Dockerfile has specifics on how to build and package the app.

Sample App Repo: https://github.com/harness-apps/easy-node-docker

To execute the local build, the first step if using the sample application is to download/clone the repository to your local machine. For the later automated build steps, Fork the sample repository.

Docker File

Building and packaging this sample application locally requires a few pieces, NPM and Docker. If you don’t have those runtimes, on a Windows Machine, you can use Chocolatey to install, or if using a Mac, Homebrew.

NPM:
choco install nodejs
brew install node

Docker:
choco install docker
brew install docker

Once you build your application, you will need to store the binaries somewhere, in this case, the Docker Image. The next step, Docker Build, will call the underlying NPM Install to start the build process.

Creating and Storing Your Image

Like any file you want to share with the world, storing them in an external spot makes them more accessible. A big benefit of using Docker as a packaging format is the ecosystem of Docker Registries out there. Your firm might have a registry provider. A good free registry for yourself is Docker Hub. If you do not have a registry available to you, you can create a Docker Hub account and create a registry, e.g “samplejs”.

Sample JS Docker Registry

With those pieces, you can build and push your image to the registry.

docker build --tag your_user/repo:tag .
docker push your_user/repo:tag

E.g in my case, at the root of the project:

docker build --tag rlachhman/samplejs:1.0.4 .
docker push rlachhman/samplejs:1.0.4

Docker Push

Can validate that this has been placed into the Docker Registry.

Local Push

Simple enough locally to get your local build and packaging in. The next step is now to externalize this, which is exactly what creating a Continuous Integration Pipeline is all about.

Your First Continuous Integration Pipeline

If you took a closer look at what your machine was doing during those local builds, the machine was bogged down for a few moments. For yourself, that is fine, but imagine having to support 10’s or 100’s or even 1000’s of engineers, this process can be taxing on systems. Luckily, modern Continuous Integration Platforms are designed to scale with distributed nodes. Harness Continuous Integration is designed to scale and simplify getting your local steps externalized; this is the Continuous Integration Pipeline. Let’s enable Harness Continuous Integration to mimic your local steps and create your first CI Pipeline. Once you are done, you will have a repeatable, consistent, and distributed build process.

There are a few Harness Objects to create along the way, which this guide will walk through step-by-step.There are two paths to take. One path is to have Harness host all of the needed infrastructure for a distributed build. The second is to bring your own infrastructure for the distributed build.

Hosted Infrastructure:

Harness CI Hosted Overview

Bring Your Own Infrastructure:

Harness CI Bring Your Own Overview

Starting off with Harness

Harness is a Platform, but we will focus on the Continuous Integration module. First, sign up for a Harness account to get started.

Harness Signup

Access To Your Sourcecode

Assuming you are leveraging GitHub, Harness will need access to the repository. For the example, providing a Personal Access Token that is scooped to “repo” will allow for connectivity. If you are leveraging the sample repository, make sure to Fork the sample repository.

If you have not created a Personal Access Token before.

  • GitHub -> Settings -> Developer Settings -> Personal Access Tokens
  • Name: harness
  • Scopes: repo
  • Expiration: 30 days

Make sure to jot down the token as the token will only be displayed once.

Repo Scope

Now you are ready to wire in the pieces to Harness Continuous Integration.

Create Your First Pipeline

In the Build Module [Harness Continuous Integration], walking through the wizard is the fastest path to get your build running. Click Get Started. This will create a basic Pipeline for you.

Get Started

Once you click Get Started, select GitHub as the repository you use, and then you can enter your GitHub Access Token that was created or being leveraged for the example.

SCM Choice

Click Continue. Then click Select Repository to select the Repository that you want to build [the sample is called easy-node-docker].

Node Docker Repo

Select the repository, then click Create Pipeline. The next step to focus on will be where you want to run the build by configuring the Pipeline.

Can leverage one of the Starter Configs or create a Starter Pipeline. In this case if leveraging the example app which is NodeJS based, leveraging the Node.js Starter Configuration works fine.

Configure Node JS

Click Continue to define what infrastructure to run the build on. To run on Harness Hosted Infrastructure, first change the Infrastructure to “Cloud”.

Where to Build

The scaffolding will take care of the NPM install for you. End goal would be to have a published Docker Image of your artifact. Can add an additional Step to take care of the Docker Push.

Add Publish

Select “Build and Push” image to Docker Registry.

Docker Publish Step

Next, create a new Docker Connector by clicking on + New Connector.

  • Name: my_docker_hub_account

My Docker Hub

Next fill out the details of your account credentials for a Docker Push.

  • Registry URL: https://index.docker.io/v2/
  • Authentication: Username and Password
  • Provider Type: DockerHub
  • Username: your_docker_hub_user
  • Password: your_docker_hub_pw

Docker Hub Details

For sensitive items such as your Docker Hub password, these can be stored as a Harness Secret.

Docker Hub Password Secret

Click Save and Continue. You can run this connection directly from the Harness Platform.

User Harness Docker

Once selected, you can run a connectivity test and you are ready to provide the registry details.

  • Name: docker_build_and_push
  • Docker Connector: my_docker_hub_account
  • Docker Repository: <your_user>/<your_repository>
  • Tags: cibuilt

Docker Build and Push

Click Apply Changes then Save.

Save Hosted

Now you are ready to run once saved.

Running Your First CI Pipeline

Executing is simple. Head back to your pipeline and click on “Run”. Unlike your local machine, where you had to wire in NPM and Docker dependencies, Harness CI will resolve these by convention.

Pipeline

Then you can select a branch to run off of and execute a step. Branch Name: main [if using the example repo]

Run Pipeline

Now you are ready to execute. Click “Run Pipeline”.

Execute Pipeline

Once a successful run, head back to Docker Hub, and cibuilt is there!

Success

This is just the start of your Continuous Integration journey. It might seem like multiple steps to get your local build in the platform, but it unlocks the world of possibilities.

Continuing on Your Continuous Integration Journey

You can now execute your builds whenever you want in a consistent fashion. Can modify the trigger to watch for SCM events so upon commit, for example, the Pipeline gets kicked off automatically. All of the objects you create are available for you to re-use. One part we did not touch upon in this example is executing your test suites in a similar format. Lastly, you can even save your backing work / have it as part of your source code. Everything that you do in Harness is represented by YAML; feel free to store it as part of your project.

CI as Code

After you have built your artifact, the next step is to deploy your artifact. This is where Continuous Delivery steps in and make sure to check out some other CD Tutorials.