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.
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.
-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.
-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.
- The first segment of the image name,
jenkins, is the name of the Docker repository.
- The second segment of the image name, also
jenkins, is the name of the image.
- The third segment,
lts-jdk11, is the tag (or version) of the image.
- A registry can contain multiple repositories, and a repository can have many tags.
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
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
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
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:
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:
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:
To build a new Docker image with the
docker build . -t myjenkins
This command builds a new image called
myjenkins. To run the new image, first stop any existing container using the
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
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:
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:
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
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
[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
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.