NOTE: Make sure you’re checking out a branch at the beginning of each section!

Building our master image

Jenkins Master

Now that we have a good base to inherit from, we can begin building out the rest of our images inheriting from that one. The next image we need is for the master. This image won’t contain too much other than generic configuration and a couple tools because we want our master image itself to be as generic as possible. The customization of each provisioned Jenkins master consists of configuration and plugins which we will package in a separate image. We will talk more about why it’s broken down this way later on. For now, let’s take a look at what we have for a Jenkins master image (modernjenkins/jenkins-master):


# images/jenkins-master/Dockerfile
FROM modernjenkins/jenkins-base
MAINTAINER matt@notevenremotelydorky

LABEL dockerfile_location= \
      image_name=modernjenkins/jenkins-master \

# Jenkins' Environment

# `/usr/share/jenkins/ref/` contains all reference configuration we want 
# to set on a fresh new installation. Use it to bundle additional plugins 
# or config file with your custom jenkins Docker image.
RUN mkdir -p /usr/share/jenkins/ref/init.groovy.d

# # Disable the upgrade banner & admin pw (we will add one later)
RUN echo 2.0 > /usr/share/jenkins/ref/jenkins.install.UpgradeWizard.state \
    && echo 2.0 > ${JENKINS_HOME}/jenkins.install.InstallUtil.lastExecVersion

# Fix up permissions
RUN chown -R ${user} "$JENKINS_HOME" /usr/share/jenkins/ref

# Install our start script and make it executable
# This script can be downloaded from
COPY files/ /usr/local/bin/
RUN chown jenkins /usr/local/bin/* && chmod +x /usr/local/bin/*

# Make our jobs dir ready for a volume. This is where job histories
# are stored and we are going to use volumes to persist them
RUN mkdir -p ${JENKINS_HOME}/jobs && chown ${user}:${group} ${JENKINS_HOME}/jobs

# Install Docker (for docker-slaves plugin)
RUN yum-config-manager --add-repo \ \
    && yum makecache fast \
    && yum install -y docker-ce \
    && yum clean all -y

# Switch to the Jenkins user from now own
USER ${user}

# Configure Git
RUN git config --global "" \
    && git config --global "CI/CD LIfe Jenkins"

# Main web interface and JNLP slaves
EXPOSE 8080 50000
ENTRYPOINT ["/usr/local/bin/"]

Looking at this Dockerfile, you may see a few new things like USER (will run the commands after this declaration as the defined user) and EXPOSE (exposes defined ports for binding to an outside port), but for the most part it’s very similar to the previous one. Set a few ENV vars, RUN a few commands etc.

We need a build script so we’ll do the same thing that we did before (except now we have the script in our repo) by creating a that can also push. Let’s just duplicate this now:

PWD: ~/code/modern-jenkins/

cd images/jenkins-master
cp -rp ../jenkins-base/ .
perl -pi -e 's~jenkins-base~jenkins-master~g'

Now we have a nice little build script for this image too. While a puppy might have died when we copy/pasta’d I didn’t hear it whimper.

There is one more file that we need for this image and it’s the startup script. Since the internet was generous enough to provide one, we should just use it. This is the script that powers the official image and I’ve got a copy of it just for you in my repo. To retrieve it, use wget:

PWD: ~/code/modern-jenkins/

cd images/jenkins-master
mkdir files
wget -O files/ \
chmod +x files/

Build the image and test it out

Now that we’ve got all the files created that our image depends on, let’s build and test it a bit.

PWD: ~/code/modern-jenkins/

# Build it
cd images/jenkins-master

# Run it
docker container run --rm -ti modernjenkins/jenkins-master bash
docker version

# You should see the Docker client version only

Commit, push, PR

The master image seems to be gtg so let’s get it integrated. You may now be seeing what we mean by ‘continuous integration’. Every time we have a small chunk of usable work, we integrate it into the master branch. This keeps change sets small and makes it easier for everyone to incorporate the steady stream of changes into their work without spending days in Git hell.

You can compare your git tree to mine at state at the unit2-part3 tag here: The Docker images are also available to pull if you don’t feel like building them for some reason.

Our next move will be to build the meat of our system: the plugins container. Awwww Yeaahhhhh

Next Post: Building the Jenkins Plugin image

Containerizing local commands

A drone in a build system may be asked to build many types of software for many different teams. Over time, the dependencies required begin to get hard to manage, especially when dealing with multiple versions. By running software straight from containers, we can eliminate the need for a lot of pre-installed software when running our builds and the headache that comes with it. Continue reading