The Jenkins logo above the Docker whale.

How to install Jenkins on Docker

Matthew Casperson

Docker emerged victorious in the battle for universal application packages. Every major operating system supports Docker images, all cloud providers support deploying Docker images, and every major tool or platform offers an official Docker image. Jenkins is no exception, providing the image jenkins/jenkins.

In this post you'll learn how to run Jenkins from a Docker image, configure it, customize it, and use the image as a replacement for a traditional package based installation.

Prerequisites

To run a Docker image, you must have Docker installed. Docker provides detailed instructions for installation on Linux, macOS, and Windows.

Note that while recent versions of Windows gained native support for running Docker images, Jenkins only provides Linux based Docker images. Windows and macOS can run Linux Docker images through virtualization, so most of the commands shown here apply equally to all operating systems, but this post will focus on Linux.

Getting started with the Jenkins Docker image

After you have Docker installed, Jenkins can be run with the command:

docker run -p 8080:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts-jdk11

Let's break this command down to understand what it's doing.

docker run is used to run a Docker image as a container.

You can think of a Docker image as a read-only artifact containing the files you need to run a particular application. Unlike most application artifacts, a Docker image contains a complete operating system and all associated system tools required to support the core application being run. In the case of Jenkins, this means the Docker image contains the files you require to support a minimal Linux operating system, along with the version of Java required to run Jenkins.

A container is an isolated environment in the OS where the Docker image is executed. Although Docker doesn't typically provide the same kind of isolation guarantees that virtual machines do, containers do provide a way to easily run trusted code side-by-side.

The -p arguments map a local port to a port exposed by the Docker container. The first argument is the local port, followed by a colon, and then the container port.

So the argument -p 8080:8080 maps local port 8080 to the container port 8080 (which is the web port), and -p 50000:50000 maps local port 50000 to the container port 50000 (which is the agent port). This means you can open http://localhost:8080 on your local machine, and Docker directs the traffic into the webserver hosted by the container.

The argument -v jenkins_home:/var/jenkins_home creates a volume called jenkins_home if it does not already exist and mounts it under the path /var/jenkins_home inside the container.

While Docker images are read-only, Docker containers expose a read/write filesystem allowing any running application to persist changes. The changes are local to the container though, and if the container is destroyed, the changes are lost. Or, if you want to use a different container, if you want to upgrade to a newer version of Jenkins, for example, the changes to your old container are also lost.

Docker volumes allow containers to persist data outside of the container's lifecycle and share it with different containers. You can think of volumes as network drives you would typically see mounted into your session when logging into an enterprise network. By saving mutable data to a volume, you can destroy and recreate your Jenkins container, or create a new container based on a newer Docker image, while retaining any changes you made to the Jenkins configuration.

The final argument jenkins/jenkins:lts-jdk11 is the name of the Docker image. This particular image can be found on Dockerhub, which is one of many Docker registries available to host Docker images.

Note that the image associated with a Docker tag can change over time. Many registries use a "floating" tag to represent the latest version of an image. In the case of the Jenkins image, the image with the tag lts-jdk11 is updated with each LTS release.

To ensure your local machine has the latest image, you must manually run docker pull jenkins/jenkins:lts-jdk11. Be aware though that any existing containers will continue to use the old image, and you must create a new container to reference any updated images.

More specific tags, like 2.303.2-lts-jdk11, are generally not overwritten, so there is no reason to run docker pull on these images.

To view the container this command created, run:

docker container ls

You will see the basic details of the container, along with a (often humorous) name like nostalgic_tharp:

CONTAINER ID   IMAGE                       COMMAND                  CREATED              STATUS              PORTS                                                                                      NAMES
801f4e834173   jenkins/jenkins:lts-jdk11   "/sbin/tini -- /usr/…"   About a minute ago   Up About a minute   0.0.0.0:8080->8080/tcp, :::8080->8080/tcp, 0.0.0.0:50000->50000/tcp, :::50000->50000/tcp   nostalgic_tharp

To define the container name, pass the --name argument:

docker run -d --name jenkins -p 8080:8080 -p 50000:50000 jenkins/jenkins:lts-jdk11

The first time you boot Jenkins, the Docker logs will contain a message like this:

Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

1883c809f01b4ed585fb5c3e0156543a

This may also be found at: /var/jenkins_home/secrets/initialAdminPassword

That random string of numbers and letters is the initial administrator password, which is required to complete the Jenkins configuration.

Open http://localhost:8080 when you see the following message in the logs:

Jenkins is fully up and running

You're now given the opportunity to complete the initial configuration of the Jenkins instance. Take a look at the previous post about traditional Jenkins installation for more details on completing this initial configuration.

You may have noticed that running Docker with the command above attaches your terminal to the container output stream. To run the Docker image in the background, use the -d or --detach argument:

docker run -d -p 8080:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts-jdk11

Adding additional software to the Jenkins server

Because Jenkins is written in Java, the default server created by running the Jenkins Docker image has most of the software required to compile and test Java applications.

To build applications written in other languages, you connect external Jenkins agents with the required software and run jobs on those. Using agents is a scalable solution, and one that you should consider if you're using Jenkins in a production environment.

For local testing though, a more convenient solution is to build a custom Docker image with the required tools baked in. To do this, you need to create a file called Dockerfile with contents similar to the following:

FROM jenkins/jenkins:lts-jdk11
USER root
RUN apt update && \
    apt install -y --no-install-recommends gnupg curl ca-certificates apt-transport-https && \
    curl -sSfL https://apt.octopus.com/public.key | apt-key add - && \
    sh -c "echo deb https://apt.octopus.com/ stable main > /etc/apt/sources.list.d/octopus.com.list" && \
    apt update && apt install -y octopuscli
USER jenkins

Dockerfile files are used to build new Docker images. You can find a complete reference of the commands available in a Dockerfile from the Docker documentation. The example above uses a small subset of the commands, but demonstrates a typical custom image based on the image provided by Jenkins.

The file starts with the FROM command, which instructs Docker to build the new image from the supplied image. This means your new image will have Jenkins and any supporting tooling already installed and configured:

FROM jenkins/jenkins:lts-jdk11

In order to install new software, you must switch to the root user. Just as with a regular Linux OS, only privileged users can install new software from a package manager:

USER root

The next command performs the software installation. This example installs the Octopus CLI using the instructions from the Octopus website:

RUN apt update && \
    apt install -y --no-install-recommends gnupg curl ca-certificates apt-transport-https && \
    curl -sSfL https://apt.octopus.com/public.key | apt-key add - && \
    sh -c "echo deb https://apt.octopus.com/ stable main > /etc/apt/sources.list.d/octopus.com.list" && \
    apt update && apt install -y octopuscli

It's considered best practice to have a regular user account run the application in the Docker container. The jenkins user was created in the base image, and so you switch back to that user with the final command:

USER jenkins

To build a new Docker image with the Dockerfile, run:

docker build . -t myjenkins

This command builds a new image called myjenkins. To run the new image, first stop any existing container using the jenkins_home volume:

docker container stop nostalgic_tharp

Then run your new image mounting the existing jenkins_home volume to retain all your existing Jenkins configuration:

docker run -p 8080:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home myjenkins

Installing additional Jenkins plugins

The easiest way to install new plugins is to use the Jenkins web UI. Any new plugins are saved to the external volume, so they're available even as you create, destroy, and update containers.

You can also automate the process of installing plugins by calling the jenkins-plugin-cli script, included in the base Jenkins image, as part of your custom Docker image.

Here is an example Dockerfile that installs the Octopus Jenkins plugin:

FROM jenkins/jenkins:lts-jdk11
USER root
RUN apt update && \
    apt install -y --no-install-recommends gnupg curl ca-certificates apt-transport-https && \
    curl -sSfL https://apt.octopus.com/public.key | apt-key add - && \
    sh -c "echo deb https://apt.octopus.com/ stable main > /etc/apt/sources.list.d/octopus.com.list" && \
    apt update && apt install -y octopuscli
RUN jenkins-plugin-cli --plugins octopusdeploy:3.1.6
USER jenkins

This Dockerfile is similar to the previous example, but includes a new RUN statement to install the Octopus plugin:

RUN jenkins-plugin-cli --plugins octopusdeploy:3.1.6

The plugin ID (octopusdeploy) and version (3.1.6) are found from the Jenkins plugin website:

Jenkins Plugin Website with the ID octopus deploy and version 3.1.6 highlighted

Publishing the custom Docker image

To publish your custom Docker image, you need an account with a Docker registry. DockerHub is a popular choice, and provides free hosting of public images.

Create a free account, and then login with the command:

docker login

To build an image that can be published to DockerHub, run the following command, replacing username with your DockerHub username:

docker build . -t username/myjenkins

Publish the image with this command:

docker push username/myjenkins

My DockerHub username is mcasperson, so I run these commands to build and publish an image:

docker build . -t mcasperson/myjenkins
docker push mcasperson/myjenkins

My custom Docker image is then available from DockerHub.

Passing Java arguments

Advanced Jenkins configuration is often performed by passing Java arguments, typically in the form of system properties.

The Jenkins Docker image allows Java arguments to be defined in the JAVA_OPTS environment variable. This environment variable is read by the Docker image script that launches Jenkins and passed as Java arguments.

To define the JAVA_OPTS environment variable, pass the --env argument to the docker run command:

docker run -p 8080:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home --env JAVA_OPTS=-Dhudson.footerURL=http://mycompany.com jenkins/jenkins:lts-jdk11

A list of Jenkins system properties can be found in the Jenkins documentation.

Passing Jenkins arguments

In addition to system properties, Jenkins also accepts a number of application arguments.

Application arguments are defined by appending them to the end of the Docker run command. The example below passes the --httpPort argument configuring Jenkins to listen on port 8081:

docker run -p 8080:8081 -p 50000:50000 -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts-jdk11 --httpPort=8081

Application arguments may also be defined in the JENKINS_OPTS environment variable:

docker run -p 8080:8081 -p 50000:50000 -v jenkins_home:/var/jenkins_home --env JENKINS_OPTS=--httpPort=8081 jenkins/jenkins:lts-jdk11 

A list of application arguments can be found in the Winstone GitHub repository. Winstone is the default embedded servlet container in Jenkins.

Backup the Docker volume

You can run the following command to backup the data saved in the Docker volume hosting the /var/jenkins_home directory. It mounts the volume in a new container, mounts the current working directory in the container's /backup directory, and creates an archive called backup.tar containing the contents of the /var/jenkins directory:

docker run --rm -v jenkins_home:/var/jenkins_home -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /var/jenkins_home

This command can be run while the Jenkins container is running because Docker volumes can be shared between running containers. However, it's recommended you stop Jenkins before performing a backup:

Even though Jenkins takes advantage of COW, it is recommended that you stop Jenkins if possible before performing a backup because the pipeline workflow XML files may get captured in an inconsistent state (for example if the backup does not take an 'instant snapshot' of every file at that exact moment).

Running Docker images as services

A production instance of Jenkins must be automatically restarted when the underlying operating system is restarted. However, this is not the default behavior of the containers you have launched using the Docker commands shown above, so any Jenkins containers will remain stopped after an OS restart.

To resolve this issue you can run a Docker container as a systemd service. This allows you to manage a Jenkins container in much the same way you would manage a Jenkins instance installed with a package manager.

To create a new systemd service, save the following contents to the file /etc/systemd/system/docker-jenkins.service:

[Unit]
Description=Jenkins

[Service]
SyslogIdentifier=docker-jenkins
ExecStartPre=-/usr/bin/docker create -m 0b -p 8080:8080 -p 50000:50000 --restart=always --name jenkins jenkins/jenkins:lts-jdk11
ExecStart=/usr/bin/docker start -a jenkins
ExecStop=-/usr/bin/docker stop --time=0 jenkins

[Install]
WantedBy=multi-user.target

To load the new service file, run the command:

sudo systemctl daemon-reload

To start the service, run the command:

sudo systemctl start docker-jenkins

To enable the service to run on restart, run the command:

sudo systemctl enable docker-jenkins

To view the service logs, run the command:

sudo journalctl -u docker-jenkins -f

Conclusion

Running Jenkins from a Docker image provides a convenient method for launching Jenkins in a self-contained and preconfigured environment.

In this post you learned how to:

  • Launch Jenkins in a Docker container
  • Install additional tools and plugins
  • Pass Java system properties and Jenkins application arguments
  • Backup the Docker volume
  • Configure a Docker container as a systemd service

Running Docker images on a workstation or server is just the beginning though. In the next post, you'll learn how to deploy Jenkins to a Kubernetes cluster.

Try our free Jenkins Pipeline Generator tool to create a Pipeline file in Groovy syntax. It's everything you need to get your Pipeline project started.

Watch our Jenkins Pipeline webinar

We host webinars regularly. See the webinars page for details about upcoming events, and live stream recordings.

Read the rest of our Continuous Integration series.

Happy deployments!

Loading...