Using Jenkins CI/CD for your NodeJS app: Building, Testing and Deploying
This is final entry on how I set up my current server architecture. You can find the second blog post here: Setting up Jenkins on your Docker instance for CI/CD
We've already learned how to setup our Jenkins instance running on Docker. This entry will teach you how to run builds, test and then deploy your apps! For this example, we will be deploying a Node application.
Creating the Node Application
If you've already created your Node app, please skip this section.
I'm just going to be creating a very simple Node app, using ExpressJS that will just return us "Hello World". You can follow along or just Fork and then Clone my repo at: https://github.com/HarveyD/node-jenkins-app-example
First let's setup our git repo. Using GitHub, create a repository. Grab the ssh url and then clone the freshly created repository: git clone git@github.com:HarveyD/node-jenkins-app-example.git
.
Now enter the directory and let's setup our package.json
by typing npm init
. Follow all the CLI prompts to get your package.json
file:
{
"name": "node-jenkins-app-example",
"version": "0.0.1",
"description": "Simple Hello World demo app.",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Harvey Delaney",
"license": "ISC"
}
Next, create your app.js file: touch app.js
. Now add ExpressJS by typing: npm install --save express
.
Using the "Hello World" example found on the Express homepage, copy the following in:
const express = require('express')
const app = express()
app.get('/', (req, res) => res.send('Hello World!'))
var server = app.listen(3000, () => {
console.log("Listening on port " + server.address().port + "...");
});
Run the app by typing node app.js
and hopefully if you go to localhost:3000
it will display "Hello World". How awesome is Express? So quick and easy to get started!
Let's also add some quick tests using Jasmine (this isn't a Jasmine tutorial so I'm gonna run through it quick). Following this blog post:
npm install --save-dev node-jasmine
npm install --save request
- Modify the
scripts.test
script inpackage.json
to be:"test": "jasmine-node spec"
. package.json
should look like:
{
"name": "node-jenkins-app-example",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "jasmine-node spec"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.16.3",
"request": "^2.85.0"
},
"devDependencies": {
"jasmine-node": "^1.14.5"
}
}
mkdir spec
cd spec
touch helloWorld-spec.js
- Add the below code to the spec file:
var request = require("request");
var base_url = "http://localhost:3000/";
var server = require("../app.js");
describe("Hello World Server", function() {
describe("GET /", function() {
it("returns status code 200", function(done) {
request.get(base_url, function(error, response, body) {
expect(response.statusCode).toBe(200);
done();
});
});
it("returns Hello World", function(done) {
request.get(base_url, function(error, response, body) {
expect(body).toBe("Hello World!");
done();
server.close();
});
});
});
});
Run npm test
and all the tests should pass. Great stuff!
Configuring Jenkins Build/Test/Deploy steps
Installing Node on Jenkins
If we want to be able to build and test our node application, we have to first install Node on Jenkins! To do this, navigate to Manage Jenkins
-> Manage Plugins
-> Search for NodeJS
and install the plugin.
Now navigate to Manage Jenkins
-> Global Tool Configuration
and look for the NodeJS heading. Install whatever version of NodeJS you require and click save:
We don't require any global packages yet, but if you need some feel free to add them.
Setting up Build and Test steps
Navigate to the project configuration settings you are wanting to setup the pipeline for and go to the Build Environment. Select the name of the Node environment we just created:
Now go to the Build heading and add the following:
Anytime Jenkins detects there has been update from GitHub (setup in my previous blog), it'll install the app's dependencies and then test the app using Jasmine (or whatever is in the test script within package.json
).
Dockerfile Node App Config
Now we have to create a Dockerfile which will instruct Docker how to deploy the app within a Docker image. Go to the root of the Node project we just created and type touch Dockerfile
and put the following into it:
FROM mhart/alpine-node
EXPOSE 3000
WORKDIR /app
COPY . /app
CMD ["node", "app.js"]
This is basically telling Docker we want to deploy our files to an Alpine Node Docker image (basically a lightweight version of a Node instance). It'll copy over our built files to the Docker image and run the Node app.
Add the following to your docker-compose.yaml file:
node-jenkins-app-example:
restart: always
image: node-jenkins-app-example
build: ./node-jenkins-app-example
container_name: node-jenkins-app-example
environment:
- VIRTUAL_HOST=PUTSUBDOMAINHERE.harveydelaney.com
- VIRTUAL_PORT=3000
Feel free to change the names of the container or the .yaml
entry title.
Deploying
If you followed my last blog entry, you would have set up Deploy Over SSH. This is a great Jenkins plugin which makes it super easy to deploy using bash commands.
Navigate to the Build Environment section of the Jenkins project configuration and use the following settings (changing it to your project name/directory structure):
After the build/test happens and passes successfully, this plugin will copy over all our files into the directory of our choosing (we can be more selective of what we copy over, but for simplicity sake I'm copying everything).
Now, all we need to do is add a step that will re-build our Docker container and run the image. For this purpose, I created a publish.sh
bash script that makes it easy to do so. On the server, in the same directory as your docker-compose.yaml
file, run touch publish.sh
to create our bash script and then copy the following into it:
#!/bin/bash
sudo docker-compose stop $1
sudo docker-compose build $1
sudo docker-compose up -d $1
Then run sudo chmod +x publish.sh
so the script can then executed. It's quite easy to read, basically stops, builds and redeploys the specified .yaml
entry within the compose file.
With this publish script, let's go back to Jenkins and tell Jenkins to run this publish.sh
script after a successful build, test and deployment of our app:
This exec command will run after the files are copied over.
A basic summary of the flow is as follows:
That's it! Repeat for any other Node apps you wish to make. If you wish to do the same for an Angular/React/Vue app, you'd need to modify the build steps to accomodate how each framework handles building apps for production. Then you'd follow very similar steps.
Hope that helps you guys out a bit, any questions don't hesitate to leave a comment or reach out to me.