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.
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.
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.
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”.
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
Can validate that this has been placed into the Docker Registry.
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:
Bring Your Own Infrastructure:
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.
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.
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.
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.
Click Continue. Then click Select Repository to select the Repository that you want to build [the sample is called easy-node-docker].
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.
- Harness Hosted Build Infrastructure
- Self-Managed Build Infrastructure
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.
Click Continue to define what infrastructure to run the build on. To run on Harness Hosted Infrastructure, first change the Infrastructure to “Cloud”.
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.
Select “Build and Push” image to Docker Registry.
Next, create a new Docker Connector by clicking on + New Connector.
- Name:
my_docker_hub_account
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
For sensitive items such as your Docker Hub password, these can be stored as a Harness Secret.
Click Save and Continue. You can run this connection directly from the Harness Platform.
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
Click Apply Changes then Save.
Now you are ready to run once saved.
If you want to use your own self-managed build infrastructure, then you should install the Kubernetes Delegate in the Kubernetes cluster of your choice.
Install Delegate
Install Delegate on Kubernetes or Docker
What is a Delegate?
Harness Delegate is a lightweight worker process that is installed on your infrastructure and communicates only via outbound HTTP/HTTPS to the Harness Platform. This enables the Harness Platform to leverage the delegate for executing the CI/CD and other tasks on your behalf, without any of your secrets leaving your network.
You can install the Harness Delegate on either Docker or Kubernetes.
Install Delegate
Create New Delegate Token
Login to the Harness Platform and go to Account Settings -> Account Resources -> Delegates. Click on the Tokens tab. Click +New Token and give your token a name `firstdeltoken`. When you click Apply, a new token is generated for you. Click on the copy button to copy and store the token in a temporary file for now. You will provide this token as an input parameter in the next delegation installation step. The delegate will use this token to authenticate with the Harness Platform.Get Your Harness Account ID
Along with the delegate token, you will also need to provde your Harness accountId as an input parameter to the delegate installation. This accountId is present in every Harness URL. For example, in the following URL
https://app.harness.io/ng/#/account/6_vVHzo9Qeu9fXvj-AcQCb/settings/overview
6_vVHzo9Qeu9fXvj-AcQCb
is the accountId.
Now you are ready to install the delegate on either Docker or Kubernetes.
- Kubernetes
- Docker
Prerequisite
Ensure that you access to a Kubernetes cluster. For the purposes of this tutorial, we will use minikube
.
Install minikube
- On Windows:
choco install minikube
- On macOS:
brew install minikube
Now start minikube with the following config.
minikube start --memory 4g --cpus 4
Validate that you have kubectl access to your cluster.
kubectl get pods -A
Now that you have access to a Kubernetes cluster, you can install the delegate using any of the options below.
- Helm Chart
- Terraform Helm Provider
- Kubernetes Manifest
Install Helm Chart
As a prerequisite, you should have Helm v3 installed on the machine from which you connect to your Kubernetes cluster.
You can now install the delegate using the Delegate Helm Chart. Let us first add the harness-delegate
helm chart repo to your local helm registry.
helm repo add harness-delegate https://app.harness.io/storage/harness-download/delegate-helm-chart/
helm repo update
helm search repo harness-delegate
You can see that there are two helm charts available. We will use the harness-delegate/harness-delegate-ng
chart in this tutorial.
NAME CHART VERSION APP VERSION DESCRIPTION
harness-delegate/harness-delegate-ng 1.0.8 1.16.0 A Helm chart for deploying harness-delegate
Now we are ready to install the delegate. The following command installs/upgrades firstk8sdel
delegate (which is a Kubernetes workload) in the harness-delegate-ng
namespace by using the harness-delegate/harness-delegate-ng
helm chart.
helm upgrade -i firstk8sdel --namespace harness-delegate-ng --create-namespace \
harness-delegate/harness-delegate-ng \
--set delegateName=firstk8sdel \
--set accountId=PUT_YOUR_HARNESS_ACCOUNTID_HERE \
--set delegateToken=PUT_YOUR_DELEGATE_TOKEN_HERE \
--set managerEndpoint=PUT_YOUR_MANAGER_HOST_AND_PORT_HERE \
--set delegateDockerImage=harness/delegate:23.02.78306 \
--set replicas=1 --set upgrader.enabled=false
PUT_YOUR_MANAGER_HOST_AND_PORT_HERE
should be replaced by the Harness Manager Endpoint noted below. For Harness SaaS accounts, you can find your Harness Cluster Location in the Account Overview page under Account Settings section of the left navigation. For Harness CDCE, the endpoint varies based on the Docker vs. Helm installation options.
Harness Cluster Location | Harness Manager Endpoint on Harness Cluster |
---|---|
SaaS prod-1 | https://app.harness.io |
SaaS prod-2 | https://app.harness.io/gratis |
SaaS prod-3 | https://app3.harness.io |
CDCE Docker | http://<HARNESS_HOST> if Docker Delegate is remote to CDCE or http://host.docker.internal if Docker Delegate is on same host as CDCE |
CDCE Helm | http://<HARNESS_HOST>:7143 where HARNESS_HOST is the public IP of the Kubernetes node where CDCE Helm is running |
Create main.tf file
Harness has created a terraform module for the Kubernetes delegate. This module uses the standard terraform Helm provider to install the helm chart onto a Kubernetes cluster whose config by default is stored in the same machine at the ~/.kube/config
path. Copy the following into a main.tf
file stored on a machine from which you want to install your delegate.
module "delegate" {
source = "harness/harness-delegate/kubernetes"
version = "0.1.5"
account_id = "PUT_YOUR_HARNESS_ACCOUNTID_HERE"
delegate_token = "PUT_YOUR_DELEGATE_TOKEN_HERE"
delegate_name = "firstk8sdel"
namespace = "harness-delegate-ng"
manager_endpoint = "PUT_YOUR_MANAGER_HOST_AND_PORT_HERE"
delegate_image = "harness/delegate:23.02.78306"
replicas = 1
upgrader_enabled = false
# Additional optional values to pass to the helm chart
values = yamlencode({
javaOpts: "-Xms64M"
})
}
provider "helm" {
kubernetes {
config_path = "~/.kube/config"
}
}
Now replace the variables in the file with your Harness Accound ID and Delegate Token values. PUT_YOUR_MANAGER_HOST_AND_PORT_HERE
should be replaced by the Harness Manager Endpoint noted below. For Harness SaaS accounts, you can find your Harness Cluster Location in the Account Overview page under Account Settings section of the left navigation. For Harness CDCE, the endpoint varies based on the Docker vs. Helm installation options.
Harness Cluster Location | Harness Manager Endpoint on Harness Cluster |
---|---|
SaaS prod-1 | https://app.harness.io |
SaaS prod-2 | https://app.harness.io/gratis |
SaaS prod-3 | https://app3.harness.io |
CDCE Docker | http://<HARNESS_HOST> if Docker Delegate is remote to CDCE or http://host.docker.internal if Docker Delegate is on same host as CDCE |
CDCE Helm | http://<HARNESS_HOST>:7143 where HARNESS_HOST is the public IP of the Kubernetes node where CDCE Helm is running |
Run terraform init, plan and apply
Initialize terraform. This will download the terraform helm provider onto your machine.
terraform init
Run the following step to see exactly the changes terraform is going to make on your behalf.
terraform plan
Finally, run this step to make terraform install the Kubernetes delegate using the Helm provider.
terraform apply
When prompted by terraform if you want to continue with the apply step, type yes
and then you will see output similar to the following.
helm_release.delegate: Creating...
helm_release.delegate: Still creating... [10s elapsed]
helm_release.delegate: Still creating... [20s elapsed]
helm_release.delegate: Still creating... [30s elapsed]
helm_release.delegate: Still creating... [40s elapsed]
helm_release.delegate: Still creating... [50s elapsed]
helm_release.delegate: Still creating... [1m0s elapsed]
helm_release.delegate: Creation complete after 1m0s [id=firstk8sdel]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Download Kubernetes Manifest Template
curl -LO https://raw.githubusercontent.com/harness/delegate-kubernetes-manifest/main/harness-delegate.yaml
Replace Variables in the Template
Open the harness-delegate.yml
file in a text editor and replace PUT_YOUR_DELEGATE_NAME_HERE
, PUT_YOUR_HARNESS_ACCOUNTID_HERE
and PUT_YOUR_DELEGATE_TOKEN_HERE
with your delegate name (say firstk8sdel
), Harness accountId, delegate token value respectively.
PUT_YOUR_MANAGER_HOST_AND_PORT_HERE
should be replaced by the Harness Manager Endpoint noted below. For Harness SaaS accounts, you can find your Harness Cluster Location in the Account Overview page under Account Settings section of the left navigation. For Harness CDCE, the endpoint varies based on the Docker vs. Helm installation options.
Harness Cluster Location | Harness Manager Endpoint on Harness Cluster |
---|---|
SaaS prod-1 | https://app.harness.io |
SaaS prod-2 | https://app.harness.io/gratis |
SaaS prod-3 | https://app3.harness.io |
CDCE Docker | http://<HARNESS_HOST> if Docker Delegate is remote to CDCE or http://host.docker.internal if Docker Delegate is on same host as CDCE |
CDCE Helm | http://<HARNESS_HOST>:7143 where HARNESS_HOST is the public IP of the Kubernetes node where CDCE Helm is running |
Apply Kubernetes Manifest
kubectl apply -f harness-delegate.yml
Prerequisite
Ensure that you have the Docker runtime installed on your host. If not, use one of the following options to install Docker:
Install on Docker
Now you can install the delegate using the following command.
docker run -d --name="firstdockerdel" --cpus="0.5" --memory="2g" \
-e DELEGATE_NAME=firstdockerdel \
-e NEXT_GEN=true \
-e DELEGATE_TYPE=DOCKER \
-e ACCOUNT_ID=PUT_YOUR_HARNESS_ACCOUNTID_HERE \
-e DELEGATE_TOKEN=PUT_YOUR_DELEGATE_TOKEN_HERE \
-e MANAGER_HOST_AND_PORT=PUT_YOUR_MANAGER_HOST_AND_PORT_HERE \
harness/delegate:22.11.77436
PUT_YOUR_MANAGER_HOST_AND_PORT_HERE
should be replaced by the Harness Manager Endpoint noted below. For Harness SaaS accounts, you can find your Harness Cluster Location in the Account Overview page under Account Settings section of the left navigation. For Harness CDCE, the endpoint varies based on the Docker vs. Helm installation options.
Harness Cluster Location | Harness Manager Endpoint on Harness Cluster |
---|---|
SaaS prod-1 | https://app.harness.io |
SaaS prod-2 | https://app.harness.io/gratis |
SaaS prod-3 | https://app3.harness.io |
CDCE Docker | http://<HARNESS_HOST> if Docker Delegate is remote to CDCE or http://host.docker.internal if Docker Delegate is on same host as CDCE |
CDCE Helm | http://<HARNESS_HOST>:7143 where HARNESS_HOST is the public IP of the Kubernetes node where CDCE Helm is running |
To use local runner build infrastructure, modify the delegate command using the instructions to install the delegate in Use local runner build infrastructure
Verify Delegate Connectivity
Click Continue and in a few moments after the health checks pass, your Delegate will be available for you to use. Click Done and can verify your new Delegate is on the list.
Helm Chart & Terraform Helm Provider
Kubernetes Manifest
Docker
You can now route communication to external systems in Harness connectors and pipelines by simply selecting this delegate via a delegate selector.
For the self-managed infrastructure, can leverage one of the Starter Configs or create a Starter Pipeline. In this case, can run the Starter Pipeline.
Click Continue to start to build out the Pipeline.
Click Continue to define what infrastructure to run the build on.
First change the infrastructure to “Kubernetes”.
Then select the drop-down “Select Kubernetes Cluster”. Then + New Connector.
In the wizard, name the Kubernetes connection “myfirstcinode”.
Click continue. With Harness, you can use the same cluster the Harness Delegate is running on by selecting “Use Credentials of a specific Harness Delegate”. The Harness Delegate will facilitate all needed work on the Kubernetes cluster.
Click continue. Now select the Harness Delegate that corresponds to your Kubernetes cluster.
Click “Save and Continue” and the connection will be validated. Back in the Pipeline Builder, “myfirstcinode” will be listed.
Provide a Namespace and OS to run.
- Namespace: default
- OS: Linux [if using Windows WSL, Linux is the correct setting].
After the Build Infrastructure is set, now time to set up the Push step to push the artifact to a Docker Registry. In the Pipeline View, click + Add Stage and create a Staged called “Push”.Then click on “Set Up Stage”.
Click on “Set Up Stage”.
In the setup of the Stage, can leverage the infrastructure that the previous artifact build was run on by selecting “Propagate from an existing stage”.
Click Continue now, you can add a Step to represent the Docker Push. Click “Add Step”.
Select “Build and Push” image to Docker Registry.
Can create a new Push connector.
Name: pushtodockerhub
Next, set up the Docker Connector by clicking on the dropdown for Docker Connector. Then create a new connector.
Can name the new docker registry connector “dockerhub”.
Click continue and can enter your credentials to Docker Hub.
- Provider Type: Docker Hub
- Docker Registry URL: https://registry.hub.docker.com/v2/
- Authentication: your_user
- Password: your_password [Will be saved as a Harness Secret]
Click Continue and select the Harness Delegate to execute on. This will be your Kubernetes infrastructure.
Click Save and Continue, and the connection will validate. Then click Finish. Lastly, enter your Docker Repository and Tag information.
- Docker Repository:
your_account/your_registry
- Tags: cibuilt
Then click “Apply Changes” and Save the Changes.
With those changes saved, you are ready to execute your first CI Pipeline.
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.
Then you can select a branch to run off of and execute a step. Branch Name: main [if using the example repo]
Now you are ready to execute. Click “Run Pipeline”.
Once a successful run, head back to Docker Hub, and cibuilt
is there!
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.
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.