Setting up a Jenkins Server with Docker – Adventures in Learning

With the upcoming end-of-life for Bamboo Cloud, I’m in the market for a new build server setup. For this1 experiment, I’m returning to an old favourite – Jenkins – paired with a potential new favourite – Docker. In this post, I describe how I’ve set up a Jenkins server in a Docker container, using the Multibranch Pipeline plugin to automatically configure a simple build2.

I’m looking to set up a Jenkins server via Docker to address some key points:

  • I want most of the configuration for the server to be under version control.
  • I want the ability to run the build server locally on my machine when I’m experimenting with new features or configurations
  • I want to easily be able to set up a build server in a new environment (e.g. on a local server, or in a cloud environment such as AWS)

Docker addresses these points pretty well, as far as I can see. I’m no Docker expert, though – this is literally the first thing I’ve tried to do with this. But it’s a good starting point.

For this article, I want to get a Jenkins server, managed with Docker, to the point where I can easily set up and teardown a container locally. For reference, I’m using Docker for Mac 1.12 and Jenkins 2.7.1 (the stable versions at the time of writing), and much of the information here came from a superb set of articles from Maxfield Steward of RiotGames.com and the documentation for the Official Jenkins Docker Image.

Steps

Go and install Docker

You will need to go and get Docker. That’s a given. I used Docker for Mac, because I’m a Mac user. Your mileage may vary.

You’ll also want Docker Compose, if you follow these instructions. Docker Compose makes working with multiple related containers easier. It comes as part of the install bundle for the Mac and Windows versions, but needs to be obtained separately for Linux.

Create Docker Files

Following the tutorial from RiotGames, I created a working directory with the following layout:

  • jenkins_docker
    • .env
    • docker-compose.yml
    • jenkins_data/
      • Dockerfile
    • jenkins_master/
      • Dockerfile
      • conf/
        • plugins.txt
    • jenkins_nginx/
      • Dockerfile
      • conf/
        • jenkins.conf
        • nginx.conf

This setup creates:

  • a ‘data volume container’ to hold the Jenkins Home directory and the Jenkins logs directory.
  • a container for the Jenkins Server itself
  • a container for nginx, to be used as a reverse proxy in front of Jenkins.

Let’s go through each file, starting with the Dockerfiles

data/Dockerfile

# Needs to match the linux version used in jenkins-master
FROM debian:jessie
MAINTAINER Robert Watkins
# user 1000 must match the user id for the jenkins user in jenkins-master
RUN useradd -d "/var/jenkins_home" -u 1000 -m -s /bin/bash jenkins
RUN mkdir -p /var/log/jenkins
RUN chown -R jenkins:jenkins /var/log/jenkins
VOLUME ["/var/log/jenkins", "/var/jenkins_home"]
USER jenkins
CMD ["echo", "Data container for Jenkins"]
view raw data_Dockerfile hosted with ❤ by GitHub

This creates a container to manage the data volumes – one of the recommended best practices for using Docker and Jenkins. By moving the Jenkins Home directory into a volume, I can easily create, destroy, and recreate the main Jenkins container – something that needs to be done when upgrading the image.

This container will hold the ‘unmanaged’ Jenkins content – things I set up through the UI (such as security & users) – as well as the build artefacts.

master/Dockerfile

FROM jenkins:2.7.1
MAINTAINER Robert Watkins
USER root
RUN mkdir /var/log/jenkins
RUN mkdir /var/cache/jenkins
RUN chown -R jenkins:jenkins /var/log/jenkins
RUN chown -R jenkins:jenkins /var/cache/jenkins
USER jenkins
ENV JAVA_OPTS="-Xmx4096m"
# install plugins; the plugins.txt file can be exported from Jenkins like this:
# JENKINS_HOST=username:password@myhost.com:port
# curl -sSL "http://$JENKINS_HOST/pluginManager/api/xml?depth=1&xpath=/*/*/shortName|/*/*/version&wrapper=plugins" | perl -pe 's/.*?<shortName>([\w-]+).*?<version>([^<]+)()(<\/\w+>)+/\1 \2\n/g'|sed 's/ /:/' > jenkins-master/conf/plugins.txt
COPY conf/plugins.txt /var/jenkins_home/plugins.txt
RUN /usr/local/bin/plugins.sh /var/jenkins_home/plugins.txt
# "For 2.x-derived images, you may also want to" - we're 2.0 dervied, so we want this
RUN echo 2.0 > /usr/share/jenkins/ref/jenkins.install.UpgradeWizard.state
# Put the log file into the log directory, which will be in the data volume
# Move the WAR out of the persisted jenkins data dir
ENV JENKINS_OPTS="--logfile=/var/log/jenkins/jenkins.log --webroot=/var/cache/jenkins/war"

This is my configuration of the Jenkins server. It’s pretty close to the example from the RiotGames tutorial, but I’ve added the section between lines 13 and 18, where I select which the plugins to be installed. If you omit this, Jenkins will prompt to install plugins for you on startup. (Actually, even with this, it prompts to install – but these will already be selected and available).

My personal plugins list, at this time, consists of the common plugins, plus the Bitbucket Branch Source Plugin, because we use Bitbucket. This plugin will allow me to create new jobs for Jenkins without having to configure Jenkins itself – an important step in keeping as much Jenkins configuration in source control as possible.

If you’re not sure what plugins you wish to use, I’d advice commenting these lines out for now, then generating a plugins.txt file and enabling the lines again later.

nginx/Dockerfile

This is a basic nginx configuration, based on the RiotGames tutorial. Rather than embed it directly, I direct interested readers to the relevant gist.

docker-compose.yml

data:
build: jenkins-data
master:
build: jenkins-master
volumes:
- .:/backup
volumes_from:
- data
ports:
- "50000:50000"
nginx:
build: jenkins-nginx
ports:
- "${HTTP_PORT}:80" # Need to make this configurable, as we'll want 80:80 in 'prod' environments
links:
- master:jenkins-master

The docker-compose.yml file ties these three containers together. The data container provides the volumes used by the master container to store Jenkins data, whilst the nginx container provides a suitable reverse proxy.

One point to note is that it’s possible to change which port on the machine running the Docket container is used to expose nginx. The default – specified in a .env file – should be 80, but that’s not suitable for a lot of developer machines (such as my own), so we need to override it.

Lastly, note that the master container mounts the present working directory as /backup – this is used later to make it easy to extract data from the container.

Build and Start The Containers

We can now use Docker-Compose to build the containers, and then start them.

$ docker-compose build
$ docker-compose up -d

At this point, the containers should be running, and you should be able to go [http://localhost/] to see your Jenkins instance (add a port to the URL as needed). When you get there, you’ll be asked to enter the initial administration password – which will be inside your container. So how do you see it? With docker-compose exec!

$ docker-compose exec master cat /var/jenkins_home/secrets/initialAdminPassword
<randomly generated password here>

Once you’ve entered the initial admin password, you will, as previously mentioned, be prompted to install plugins. After that, you’ll need to make your first user, and your first job.

Backing up Jenkins Data

An important step is to know how to back up and restore your Jenkins data. The exact way to do this depends on how you’ve configured your Docker containers – assuming you’ve used a data volume container like I did, you can issue a command to extract data for backup purposes:

$ docker-compose stop
$ docker-compose run --rm master tar cvfz /backup/backups.tgz /var/jenkins_home
$ docker-compose up -d

If you need to restore this data, you run the exec in reverse:

$ docker-compose stop
$ docker-compose run --rm master bash -c "cd /var/jenkins_home && tar xvfz /backup/backup.tgz --strip 1"
$ docker-compose up -d

The server that you run the Docker containers on can then be configured to perform the backups on a regular basis via a cron task or similar.

Deploying to a Production Server

This step I haven’t yet gotten around to; I’ve still got several months to get off Bamboo Cloud, and I haven’t decided exactly where I’ll host the Jenkins server (assuming that’s what I end up with).

One likely place, however, will be on Amazon’s OpsWorks infrastructure – we use that for our production servers now, so putting a new instance up there will be straight forward. It will require building a new Chef recipe for deployment, but that’s reasonably straightforward.

Where to from here?

At this point, if you’ve been following along, you’ve got a suite of Docker containers that you can use to run a Jenkins server. I’d suggest committing the files you’ve created along the way to version control, so you can keep track of the changes you’re making.

After that – create some jobs in Jenkins! Get building. Along those lines, my next post in this series will be about what’s involved in getting a multi-branch pipeline configured for Maven. Until then, happy reading.


  1. For a previous, less successful, experiment, see my post on Bitbucket Pipelines 
  2. More complex build scenarios will come later, in subsequent blog posts. 

Author: Robert Watkins

My name is Robert Watkins. I am a software developer and have been for over 20 years now. I currently work for people, but my opinions here are in no way endorsed by them (which is cool; their opinions aren’t endorsed by me either). My main professional interests are in Java development, using Agile methods, with a historical focus on building web based applications. I’m also a Mac-fan and love my iPhone, which I’m currently learning how to code for. I live and work in Brisbane, Australia, but I grew up in the Northern Territory, and still find Brisbane too cold (after 22 years here). I’m married, with two children and one cat. My politics are socialist in tendency, my religious affiliation is atheist (aka “none of the above”), my attitude is condescending and my moral standing is lying down.

3 thoughts on “Setting up a Jenkins Server with Docker – Adventures in Learning”

    1. UUID 1000 is what is used by the Jenkins Docker container; if you can’t live with that, you’ll need to change it there. Fortunately, you can do that at build time, using the –build-arg parameter (e.g. –build-arg uid=someothervalue).

      For the full list of configurable parameters, see the source code for the Jenkins Docker container

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: