1. Overview
The Jenkins server is a versatile environment for process automation written mainly in Java. It enables effective pipeline management by employing scripts that define each step. While the deployment process of Jenkins can be fairly straightforward, we might still encounter environment-specific issues.
In this tutorial, we’ll go through a manual Jenkins installation and check common pitfalls that may prevent the service from starting. First, we’ll see a basic package install procedure. After that, we’ll explore the main Jenkins dependency and see how its absence reflects on our deployment. Next, we’ll consider reasons for problematic Jenkins service startup linked with the dependency configuration, as well as potential fixes and workarounds. Finally, we’ll demonstrate a more straightforward and robust way to deploy Jenkins.
The code in this tutorial was tested on Debian 12 (Bookworm) with GNU Bash 5.2.15. It should work in most POSIX-compliant environments unless otherwise specified.
2. Install Jenkins Package
Although we can run Jenkins in many ways, the classic package installation via a package manager is still a common deployment method.
For that, let’s first ensure we have the proper repository setup:
$ wget --output-document=/usr/share/keyrings/jenkins-keyring.asc https://pkg.jenkins.io/debian/jenkins.io-2023.key
In this case, we use the wget tool to download the Jenkins repository key for its Debian-compatible package to /usr/share/keyrings/jenkins-keyring.asc. To find the latest key, we can visit https://pkg.jenkins.io/debian/.
Now, let’s configure the repository within a separate file in /etc/apt/sources.list.d/jenkins.list:
$ echo 'deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] https://pkg.jenkins.io/debian binary/' >> /etc/apt/sources.list.d/jenkins.list
At this point, we should be ready to update the package list and perform the installation:
$ apt-get update && apt-get install jenkins
As an alternative, we can manually install the generic Jenkins WAR package from https://get.jenkins.io/war-stable/.
Critically, the installation procedure could attempt to run Jenkins, which may result in issues.
3. Jenkins Problems
For different reasons, we might end up without the full shell environment variable setup for Jenkins. While this shouldn’t prevent the service from starting, it might be a symptom of a bigger issue:
- post-installation scripts didn’t run
- general misconfiguration
- corrupt or missing configuration files
- corrupt or missing executables
In these cases, the service might exhibit problems starting.
Since we know where the service unit file is, if that’s in place and functioning, the only remaining component that can fail is the Jenkins startup script usually located in /usr/bin/jenkins[.sh]:
$ grep 'die ' /usr/bin/jenkins -C 1
if $required && [ -z "${val}" ]; then
die "check_env: ${var} must be non-empty"
fi
--
if [ -n "${JENKINS_HOME}" ]; then
[ -d "${JENKINS_HOME}" ] || die "${JENKINS_HOME} is not a directory"
fi
[ -f "${JENKINS_WAR}" ] || die "${JENKINS_WAR} is not a file"
infer_java_cmd || die 'failed to find a valid Java installation'
Here, we use grep to find what conditions are enough for the startup to fail:
- missing or incorrect required startup variable value (only $JENKINS_WAR is required)
- problems with the $JENKINS_HOME value that the service file sets
- issues with the $JENKINS_WAR file
- Java installation problems
Still, even deleting the main config.xml file in $JENKINS_HOME doesn’t prevent Jenkins from starting.
Since the other conditions are rarely in place, Java is usually the main root of Jenkins startup issues.
4. Jenkins Java Dependency
The main external dependency of Jenkins is Java. Because of this, if we don’t have a Java Runtime Environment (JRE) or Java Development Kit (JDK) at the time of installation, Jenkins installs, but fails to start:
$ apt-get install jenkins
[...]
Created symlink /etc/systemd/system/multi-user.target.wants/jenkins.service → /lib/systemd/system/jenkins.service.
Job for jenkins.service failed because the control process exited with error code.
See "systemctl status jenkins.service" and "journalctl -xeu jenkins.service" for details.
invoke-rc.d: initscript jenkins, action "start" failed.
● jenkins.service - Jenkins Continuous Integration Server
Loaded: loaded (/lib/systemd/system/jenkins.service; enabled; preset: enabled)
Active: activating (auto-restart) (Result: exit-code) since Thu 2024-02-02 10:00:00 UTC; 10ms ago Process: 3828 ExecStart=/usr/bin/jenkins (code=exited, status=1/FAILURE)
Main PID: 6660 (code=exited, status=1/FAILURE)
CPU: 6ms
Feb 02 10:00:00 xost systemd[1]: jenkins.service: Main process exited, code=exited, status=1/FAILURE
Feb 02 10:00:00 xost systemd[1]: jenkins.service: Failed with result 'exit-code'.
Feb 02 10:00:00 xost systemd[1]: Failed to start jenkins.service - Jenkins Continuous Integration Server.
dpkg: error processing package jenkins (--configure):
installed jenkins package post-installation script subprocess returned error exit status 1
Errors were encountered while processing:
jenkins
E: Sub-process /usr/bin/dpkg returned an error code (1)
Further, if we look at the whole error with journalctl, we can see the culprit:
$ journalctl -xeu jenkins.service
Feb 02 10:00:00 debian systemd[1]: Starting jenkins.service - Jenkins Continuous Integration Server...
Subject: A start job for unit jenkins.service has begun execution
Defined-By: systemd
Support: https://www.debian.org/support
A start job for unit jenkins.service has begun execution.
The job identifier is 128.
Feb 02 10:00:00 xost jenkins[6660]: jenkins: failed to find a valid Java installation
Feb 02 10:00:00 xost systemd[1]: jenkins.service: Main process exited, code=exited, status=1/FAILURE
As we can see, the problem is clearly stated as failed to find a valid Java installation.
So, let’s supply Java via the OpenJDK:
$ apt-get install openjdk-17-jre
After the installation, we can check the version:
$ java -version
openjdk version "17.0.9" 2023-10-17
OpenJDK Runtime Environment (build 17.0.9+9-Debian-1deb12u1)
OpenJDK 64-Bit Server VM (build 17.0.9+9-Debian-1deb12u1, mixed mode, sharing)
Now, let’s attempt to start the Jenkins service again and check its status via systemctl:
$ systemctl start jenkins.service
$ systemctl status jenkins.service
● jenkins.service - Jenkins Continuous Integration Server
Loaded: loaded (/lib/systemd/system/jenkins.service; enabled; preset: enabled)
Active: active (running) since Thu 2024-02-02 16:00:01 UTC; 966ms ago
Main PID: 6111 (java)
Tasks: 40 (limit: 1102)
Memory: 370.4M
CPU: 24.003s
CGroup: /system.slice/jenkins.service
└─6111 /usr/bin/java -Djava.awt.headless=true -jar /usr/share/java/jenkins.war --webroot>
[...]
At this point, everything seems fine with the service. Yet, there are situations when just having any Java installation isn’t sufficient.
5. Jenkins Java Configuration
Sometimes, we might have both Java and Jenkins, but the latter still fails to start.
5.1. Java Availability and $PATH
One fairly simple but often overlooked step is the $PATH environment variable setup concerning Java.
There are several reasons why the java binary and command may not be immediately available after installing a JRE or JDK, depending on the installation method:
- the installer might not attempt to change $PATH
- the installer might place the Java installation at an unknown location without proper symbolic linking
- changes to $PATH might not be reflected
Since there are many ways to install Java, these scenarios are fairly common, especially in cloned environments.
However, even the simple scenario of not restarting a shell session and attempting to use Java immediately after installing could lead to a self-explanatory problem:
$ java
bash: /usr/bin/java: No such file or directory
In any case, the java executable should be available to the context that attempts to run Jenkins.
5.2. Java Version Support
Different Jenkins releases and versions may expect different Java versions, as per the official documentation:
+------------------------------+---------------------------------+-----------------------+
| Supported Java versions | Long term support (LTS) release | Weekly release |
+------------------------------+---------------------------------+-----------------------+
| Java 11, Java 17, or Java 21 | 2.426.1 (November 2023) | 2.419 (August 2023) |
| Java 11 or Java 17 | 2.361.1 (September 2022) | 2.357 (June 2022) |
| Java 8, Java 11, or Java 17 | 2.346.1 (June 2022) | 2.340 (March 2022) |
| Java 8 or Java 11 | 2.164.1 (March 2019) | 2.164 (February 2019) |
+------------------------------+---------------------------------+-----------------------+
Further, while agents usually only notify of any discrepancies, the controller node requires a supported Java version. To facilitate notifications, we can use the Versions Node Monitors plugin.
5.3. Set Java Version
Many systems have several installations of Java. For example, there can be different versions of a JRE for testing, JDK for development, or a mix between both.
Since Jenkins just calls the java executable, it might not accept the default version, resulting in clear errors such as Incorrect Java Version found, failed to find a valid Java installation or even instructions on what to do:
Feb 02 10:06:56 xost jenkins[6111]: Running with Java 9 from /usr/lib/jvm/java-9-openjdk-amd64, which is older than the minimum required version (Java 11).
Feb 02 10:06:56 xost jenkins[6111]: Supported Java versions are: [11, 17, 21]
Feb 02 10:06:56 xost jenkins[6111]: See https://jenkins.io/redirect/java-support/ for more information.
There are several ways to ensure the correct executable spawns Jenkins:
- modify service file such as /lib/systemd/system/jenkins.service for systemd
- modify main script at /usr/bin/jenkins
- set default Java version
While modifications to both the service file and the main script aren’t recommended, we can still perform them for debugging and checks.
For instance, systemd service files provide the Environment directive for setting relevant environment variables:
$ cat /lib/systemd/system/jenkins.service
[...]
[Unit]
Description=Jenkins Continuous Integration Server
Requires=network.target
After=network.target
[Service]
Type=notify
NotifyAccess=main
ExecStart=/usr/bin/jenkins
Restart=on-failure
SuccessExitStatus=143
Environment="JAVA_HOME=/custom/java/path"
[...]
Here, we modify the $JAVA_HOME variable, through the value of which /usr/bin/jenkins performs checks:
$ cat /usr/bin/jenkins
[...]
infer_java_cmd() {
if [ -n "${JENKINS_JAVA_CMD}" ] && [ -x "${JENKINS_JAVA_CMD}" ]; then
return 0
fi
if [ -n "${JAVA_HOME}" ] && [ -x "${JAVA_HOME}/bin/java" ]; then
JENKINS_JAVA_CMD="${JAVA_HOME}/bin/java"
return 0
fi
JENKINS_JAVA_CMD="$(command -v java)" || return "$?"
}
[...]
Of course, we can directly change the latter for debugging purposes.
Still, a better approach is to employ a tool such as update-alternatives or similar, which selects the default executable for a given process:
$ update-alternatives --config java
This way, we have better control and visibility of the changes, while maintaining compatibility with future updates.
5.4. Install Older or Alternative Java Version
In rare cases, we might require a version of Java that’s not directly available from the repositories of our current distribution. Even in such instances, we have a way to install the necessary components.
For example, we can find older Java versions under the official Oracle Java archive page. We can acquire releases from as early as version 1.
Similarly, OpenJDK offers an archive that spans versions starting from 9 GA.
Notably, installing these packages may be non-trivial in some cases mainly due to deprecated dependencies. Still, we might find native packages for our Linux distribution in older or custom repositories.
6. Avoid Jenkins Deployment Issues
Naturally, potential problems with the Jenkins setup can be circumvented by using a ready-made container.
For example, Dockerhub hosts the official Docker jenkins image based on the minimalistic Alpine Linux:
$ docker run --name jenkins --publish 8080:8080 --detach jenkins/jenkins:lts
With this fairly basic docker command, we perform several actions:
- create a new container based on the image jenkins/jenkins:lts
- –name the container jenkins
- –publish port 8080 from the container
- –detach the container, so it runs in the background
At this point, we have a full-fledged installation of Jenkins to play around with. Notably, stopping this container resets it entirely, as no data would be preserved.
However, we can add –volume /var/data/jenkins_home:/var/jenkins_home to the command above. This way, any data in the /var/jenkins_home directory can be preserved under the local path /var/data/jenkins_home between container resets.
7. Conclusion
In this article, we talked about problems when deploying or starting the Jenkins service.
Although it uses a fairly monolithic installation package, Jenkins still has external dependencies, which may cause problems if not installed or configured correctly.