Node JS Project with Jenkins: 5 Proven Pipeline Fixes
Deploying a Node JS project with Jenkins takes one Jenkinsfile: check out the code, install dependencies, run the tests, build, deploy. The pipeline in the first version of this guide looked right but failed on its first stage in a real Jenkins, and its install steps (Java 11, apt-key) no longer work with current Jenkins. This rewrite runs every pipeline on Jenkins and shows the console output, so you can see which line breaks and how to fix it.
Lab setup: the official jenkins/jenkins:lts-jdk21 container (Jenkins 2.580.2) with the Pipeline, Git and NodeJS plugins, a NodeJS tool named NodeJS pointing to Node.js 24.22.2, and a small sample app (an HTTP server with a /health URL and one node --test test) in a Git repository served by git daemon on git://localhost/app.git. The jobs were created by a startup script instead of UI clicks; everything else is a normal Jenkins.
Contents
- Install Jenkins for a Node JS project with Jenkins in 2026
- The old Jenkinsfile on a real Jenkins
- Next failure: npm run build
- Errors you will meet with a Node JS project with Jenkins
- node: not found
- Local repository refused
- npm ci without a lockfile
- A working Jenkinsfile for a Node JS project with Jenkins
- Docker and Kubernetes stages
- Checklist for a Node JS project with Jenkins
Install Jenkins for a Node JS project with Jenkins in 2026
The old install steps used openjdk-11-jdk and apt-key add. Both are out of date. The Jenkins Java support policy lists Java 11 as unsupported since the 2.462.3 LTS (October 2024) and Java 17 since 2.541.3 (March 2026); supported today are Java 21 and Java 25, and "if you install an unsupported Java version, your Jenkins controller will not run". The current Debian/Ubuntu steps from the Jenkins installation guide are:
sudo apt update
sudo apt install fontconfig openjdk-21-jre
sudo wget -O /etc/apt/keyrings/jenkins-keyring.asc \
https://pkg.jenkins.io/debian-stable/jenkins.io-2026.key
echo "deb [signed-by=/etc/apt/keyrings/jenkins-keyring.asc]" \
https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
/etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt update
sudo apt install jenkins
The key file name contains the year, so copy it from the guide when you install rather than from an old blog post. After the install, unlock Jenkins with the password in /var/lib/jenkins/secrets/initialAdminPassword, install the suggested plugins, then add the Jenkins NodeJS plugin. On the global tools page (JENKINS_URL/configureTools, per the plugin README), add a NodeJS installation named NodeJS, either with "Install automatically" or with the path of a Node.js already on the agent. The name is what the Jenkinsfile refers to.
The old Jenkinsfile on a real Jenkins
This is the Jenkins NodeJS pipeline from the first version of the post, with only the repository URL changed to the lab repository:
pipeline {
agent any
environment {
NODEJS_HOME = tool name: 'NodeJS', type: 'jenkins.plugins.nodejs.tools.NodeJSInstallation'
PATH = "$NODEJS_HOME/bin:${env.PATH}"
}
stages {
stage('Checkout Code') {
steps {
git 'git://localhost/app.git'
}
}
stage('Install Dependencies') {
steps {
sh 'npm install'
}
}
stage('Run Tests') {
steps {
sh 'npm test'
}
}
stage('Build') {
steps {
sh 'npm run build'
}
}
stage('Deploy') {
steps {
echo 'Deploying application...'
}
}
}
}
It fails before any npm command runs:
> git rev-parse refs/remotes/origin/master^{commit} # timeout=10
> git rev-parse origin/master^{commit} # timeout=10
ERROR: Couldn't find any revision to build. Verify the repository and branch configuration for this job.
ERROR: Maximum checkout retry attempts reached, aborting
Stage "Install Dependencies" skipped due to earlier failure(s)
...
Finished: FAILURE
Every Node JS project with Jenkins starts with this checkout, so it matters. The git step documentation says it plainly: branch is optional and the "Default is 'master'". GitHub, GitLab and current git init setups usually create main, so a short git 'url' looks for a branch that does not exist. Always name the branch: git url: '...', branch: 'main'.
Next failure: npm run build
With only the branch fixed, the old pipeline gets further. The tool call inside environment works (npm was found), install and tests pass, and then:
+ npm run build
npm error Missing script: "build"
npm error
npm error To see a list of scripts, run:
npm error npm run
Stage "Deploy" skipped due to earlier failure(s)
ERROR: script returned exit code 1
Finished: FAILURE
Many Node.js services, like this sample server, have no build step. Use npm run build --if-present, which runs the script only when package.json defines it. In the fixed pipeline below that stage passes silently.
Errors you will meet with a Node JS project with Jenkins
node: not found
This is the most common Jenkins NodeJS error. Jenkins does not add Node.js to PATH by itself. A stage that runs node without a configured tool fails with exit code 127:
+ node --version
/var/jenkins_home/workspace/2-no-tool@tmp/durable-8f802847/script.sh.copy: 1: node: not found
ERROR: script returned exit code 127
The cleanest fix is the declarative tools { nodejs 'NodeJS' } block, which the Jenkins NodeJS plugin provides. It installs (if needed) and adds the tool to PATH for every stage, without the manual NODEJS_HOME/PATH lines.
Local repository refused
Testing a pipeline against a repository on the Jenkins server itself with file:// is blocked by the Git plugin:
ERROR: Checkout of Git remote 'file:///srv/git/app.git' aborted because it references a local directory, which may be insecure. You can allow local checkouts anyway by setting the system property 'hudson.plugins.git.GitSCM.ALLOW_L
Use a real Git URL (HTTPS or SSH with credentials, or git:// as in this lab) instead of turning the protection off.
npm ci without a lockfile
npm ci is the right command for CI because it installs exactly what package-lock.json says. On a branch without the lockfile it stops:
+ npm ci
npm error code EUSAGE
npm error
npm error The `npm ci` command can only install with an existing package-lock.json or
npm error npm-shrinkwrap.json with lockfileVersion >= 1. Run an install with npm@5 or
npm error later to generate a package-lock.json file, then try again.
ERROR: script returned exit code 1
Commit package-lock.json to the repository. This failure is useful: it tells you the build would otherwise install whatever versions npm resolves that day.
A working Jenkinsfile for a Node JS project with Jenkins
This version of the Node JS project with Jenkins pipeline fixes all of the above and adds a simple deploy with a smoke test. Each build is copied to its own release folder, a current symlink points to the active one, and the stage starts the app and checks /health:
pipeline {
agent any
tools { nodejs 'NodeJS' }
options { disableConcurrentBuilds() }
stages {
stage('Checkout') {
steps {
git url: 'git://localhost/app.git', branch: 'main'
}
}
stage('Install') {
steps {
sh 'node --version && npm --version'
sh 'npm ci'
}
}
stage('Test') {
steps {
sh 'npm test'
}
}
stage('Build') {
steps {
sh 'npm run build --if-present'
}
}
stage('Deploy') {
steps {
sh '''
REL=$JENKINS_HOME/deploy/releases/$BUILD_NUMBER
mkdir -p "$REL"
cp package.json package-lock.json server.js "$REL"/
ln -sfn "$REL" $JENKINS_HOME/deploy/current
cd $JENKINS_HOME/deploy/current
PORT=3001 node server.js & PID=$!
sleep 1
curl -fsS http://localhost:3001/health
curl -fsS http://localhost:3001/
kill $PID
'''
}
}
}
post {
success { archiveArtifacts artifacts: 'package.json,server.js', fingerprint: true }
failure { echo 'Build failed: the current symlink was not changed' }
}
}
The console output of the run, trimmed to the commands and their results:
+ node --version
v24.22.2
+ npm --version
11.19.1
+ npm ci
up to date, audited 1 package in 271ms
found 0 vulnerabilities
+ npm test
> hello-app@1.0.0 test
> node --test
✔ adds numbers (1.054535ms)
ℹ tests 1
ℹ pass 1
ℹ fail 0
+ npm run build --if-present
+ ln -sfn /var/jenkins_home/deploy/releases/1 /var/jenkins_home/deploy/current
+ PORT=3001 node server.js
listening on 3001
+ curl -fsS http://localhost:3001/health
ok
+ curl -fsS http://localhost:3001/
Hello from Jenkins build 1
Archiving artifacts
Recording fingerprints
Finished: SUCCESS

git 'url' step looks for master; the fixed pipeline deploys and passes the health check.In this lab the deploy target of the Node JS project with Jenkins is a folder on the Jenkins machine. On a real server, replace the copy with rsync or scp to the target over SSH (with the key stored as a Jenkins credential), and restart the service with your process manager (systemd, PM2 or a container). Rolling back is then pointing current at the previous release folder and restarting.
Docker and Kubernetes stages
The old post's advanced Jenkins NodeJS pipeline built an image with docker build, pushed it inside withDockerRegistry and ran kubectl apply. Those stages need Docker on the agent, registry credentials and a cluster, none of which were part of this lab, so they are not repeated here as tested code. Two points hold either way: tag images with something unique such as $BUILD_NUMBER or the Git commit instead of only latest, and keep the registry password in Jenkins credentials. For faster image builds see Docker BuildKit tips, and for a full Jenkins deploy of a Java app see Jenkins CI/CD for a Java web app.
Checklist for a Node JS project with Jenkins
- Jenkins on Java 21 or 25, repository key from the current install guide.
- Jenkins NodeJS plugin with a named installation, used through
tools { nodejs 'NodeJS' }. git url: ..., branch: 'main': the default branch of thegitstep is master.npm ciwith a committedpackage-lock.json;npm run build --if-present.- A deploy step that ends with a health check, so a broken release fails the build.
With these five fixes, a Node JS project with Jenkins builds the same way on every run. The steps and options used here are documented in the Jenkins git step reference and the Jenkins Linux installation guide.
Comments
Post a Comment