Today I spent some time exploring Jenkins in my homelab environment.

For those who somehow managed to avoid Jenkins during the last decade: Jenkins is one of the most widely used open-source automation servers available today. Originally released in 2011 as a fork of the Hudson project, Jenkins has become one of the cornerstones of modern DevOps workflows. It is primarily used for Continuous Integration (CI) and Continuous Delivery (CD), automating software builds, tests, deployments, and all kinds of recurring administrative tasks. If a process can be automated, Jenkins can probably handle it. 😎

Up until now, I have been using two classic cron jobs in my homelab. Their purpose is quite simple: every night they use rsync to create backups of two internet-facing Nextcloud instances.

The setup has been working perfectly fine for quite some time. However, when it comes to monitoring and logging, cron is still very much the tool of choice for command-line enthusiasts and Unix veterans. You typically end up digging through log files, manually checking whether jobs executed successfully, and occasionally wondering whether a failed backup happened three days ago or three weeks ago.

Of course, cron is incredibly reliable and lightweight. But let’s be honest: it doesn’t exactly offer the most modern user experience.

Since Jenkins will most likely play a bigger role in my professional environment in the future, I decided to finally deploy it in my homelab as well and gain some hands-on experience.

One noteworthy detail is that Jenkins runs on the same machine that already hosts GitLab. Since GitLab is already listening on TCP port 8080, Jenkins had to be configured to use TCP port 8090.

Current Jenkins Configuration

At the moment, the Jenkins configuration is intentionally simple.

I created two jobs of type Freestyle Project. Similar to my previous cron setup, each job has its own schedule defining when it should run. The actual backup operation itself is nothing more than the execution of a local shell script.

So far everything has been working flawlessly. Jenkins provides a convenient web interface where I can immediately see whether jobs have run successfully, when they were executed, how long they took, and what output they generated.

Compared to manually inspecting cron logs, this already feels like a significant quality-of-life improvement. 😊

One additional tweak was required. Jenkins itself runs under its dedicated jenkins service account, while my backup scripts expect to be executed as the user raphael. Rather than redesigning permissions and ownerships, I simply configured the build step to launch the scripts via sudo -u raphael. As a result, Jenkins remains responsible for scheduling and monitoring the jobs, while the actual backup logic runs under the account that already has access to the required files and directories. 😎

Exporting Job Configurations as XML

One particularly useful feature I discovered is that Jenkins allows every job configuration to be exported as XML.

To retrieve a job configuration, simply append /config.xml to the job URL and authenticate with a valid Jenkins user account:

curl -u <user>:<password> \
http://192.168.10.50:8090/job/Backup-Sofie/config.xml

Interestingly, directly accessing the XML configuration through a browser did not work for me. However, using curl from the command line worked perfectly.

The resulting XML contains the complete job definition, making it useful not only for documentation purposes but also as a lightweight backup of the Jenkins configuration.

Backup-CVJM Configuration

raphael@hp1:~$ curl -u <user>:<password> http://192.168.10.50:8090/job/Backup-CVJM/config.xml
<?xml version='1.1' encoding='UTF-8'?>
<project>
  <actions/>
  <description>rsync</description>
  <keepDependencies>false</keepDependencies>
  <properties/>
  <scm class="hudson.scm.NullSCM"/>
  <canRoam>true</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <triggers>
    <hudson.triggers.TimerTrigger>
      <spec>H 1 * * *</spec>
    </hudson.triggers.TimerTrigger>
  </triggers>
  <concurrentBuild>false</concurrentBuild>
  <builders>
    <hudson.tasks.Shell>
      <command>sudo -u raphael /raid5/shared/shared/ablage/nextcloud_backup_and_restore/cvjm_backup_nextcloud_remote.sh</command>
      <configuredLocalRules/>
    </hudson.tasks.Shell>
  </builders>
  <publishers/>
  <buildWrappers/>
</project>

Backup-Sofie Configuration

raphael@hp1:~$ curl -u <user>:<password> http://192.168.10.50:8090/job/Backup-Sofie/config.xml
<?xml version='1.1' encoding='UTF-8'?>
<project>
  <description>rsync</description>
  <keepDependencies>false</keepDependencies>
  <properties/>
  <scm class="hudson.scm.NullSCM"/>
  <canRoam>true</canRoam>
  <disabled>false</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <triggers>
    <hudson.triggers.TimerTrigger>
      <spec>H 2 * * *</spec>
    </hudson.triggers.TimerTrigger>
  </triggers>
  <concurrentBuild>false</concurrentBuild>
  <builders>
    <hudson.tasks.Shell>
      <command>sudo -u raphael /raid5/shared/shared/ablage/nextcloud_backup_and_restore/sofie_backup_nextcloud_remote.sh</command>
      <configuredLocalRules/>
    </hudson.tasks.Shell>
  </builders>
  <publishers/>
  <buildWrappers/>
</project>

Conclusion

From a purely technical perspective, replacing two perfectly functional cron jobs with Jenkins is probably a textbook example of overengineering. 😄

On the other hand, Jenkins provides centralized logging, execution history, status information, and a user-friendly web interface that makes monitoring scheduled tasks significantly more comfortable.

For now, Jenkins is simply responsible for launching two backup scripts. But every homelab experiment starts small, and experience shows that once Jenkins is available, it rarely stays limited to just two jobs.

Let’s see where this journey leads next. 🚀







By raphael

Leave a Reply