While preparing for a knowledge session about Jenkins, I was looking for a way for each participant to run Jenkins without having to actually install it. My thoughts went to Docker immediately. Jenkins has some tutorials on the website where they use Jenkins from a Docker container (for example: https://www.jenkins.io/doc/tutorials/build-a-node-js-and-react-app-with-npm/ ).
In this tutorial they create a network and run two docker images. One of the images is the (adapted) Jenkins docker image. The other image is the docker:dind image. This image is needed to be able to execute docker commands from inside the Jenkins instance. For example, if you want to use a docker image as an agent for a pipeline you will need this. They also create some volumes, to save the Jenkins data and make a connection to the local server’s home folder. This last connection is needed if you want to be able to run Jenkins pipelines from a local repository.
You need to use some very lengthy commands every time to start all this up. Since I was preparing this knowledge session over several days, I didn’t feel like this was very efficient. I decided to create a docker-compose file, so I wouldn’t need to remember or save all the commands to start up the docker containers. I also needed adaptations to include Chrome in my container in order to run some Angular tests. Therefore, I created a Dockerfile, to incorporate Chrome into the docker container.
I will break both files up into smaller pieces to explain what each piece does. At the end I will post the complete files.
This line tells docker on which existing image to base the docker image created with this Dockerfile. I chose one of the official Jenkins images, a long-term support (lts) image containing Jenkins and jdk-11.
The user is set to root in order to install some things we need.
RUN apt-get update && apt-get install -y apt-transport-https \ ca-certificates curl gnupg2 \ software-properties-common RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - RUN apt-key fingerprint 0EBFCD88 RUN add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/debian \ $(lsb_release -cs) stable" RUN apt-get update && apt-get install -y docker-ce-cli
The lines above install a docker engine in the image. I copied this from the tutorial previously mentioned.
RUN apt-get update && apt-get install -y wget RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - RUN echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list RUN apt-get update RUN apt-get install -y google-chrome-stable xvfb ENV CHROME_BIN='/usr/bin/google-chrome-stable'
The remaining lines install google Chrome and set the CHROME_BIN environment variable to the correct path.
The user is then set back to Jenkins.
The docker-compose.yml file
The information that I placed in the docker-compose file is similar to the information found in the commands used in the Jenkins tutorial.
The line above defines the version of the docker compose file format.
A network is defined, which is called Jenkins. This network has no further specifications.
volumes: jenkins-data: jenkins-docker-certs:
Here, two volumes are defined. The first volume will be used to store the Jenkins data. The second one will be used to map the docker certificates. This is needed for the Jenkins agent to connect to the docker daemon.
In this block the two services will be defined: the Jenkins agent and the docker in docker (dind).
All settings for the Jenkins agent are defined in this block.
The image name, if not set docker will generate a (far less descriptive) name for you.
This line specifies that the image needs to be built from a Jenkinsfile that is located at the same path as where this docker-compose file is located. The name of the Jenkinsfile is simply Jenkinsfile (the default). If your Jenkinsfile is placed in another folder, you can specify the path here instead of “.”. If the name if your Jenkinsfile is different you can also specify that by using some more configuration options (see: https://docs.docker.com/compose/compose-file/compose-file-v3/#build).
The line above ensures that the containers are restarted if killed by unexpected behaviour such as the docker daemon crashing. It will not restart if the container is manually stopped.
networks: - jenkins
This connects the Jenkins-agent to the Jenkins network, so that it can communicate with the other containers in the network.
ports: - 8080:8080 - 50000:50000
The ports 8080 and 50000 are exposed to the same port on the host machine. Port 8080 is used by the Jenkins agent to display the Jenkins UI. Port 50000 is used to communicate with other Jenkins agents. You can remove the last one if you don’t need it.
This makes sure you can access the docker container with a terminal.
volumes: - jenkins-data:/var/jenkins_home - jenkins-docker-certs:/certs/client:ro - <your home path>:/home
The first line stores the data from Jenkins_home in the Jenkins-data volume. This ensures the data is accessible from the dind container and that it is persisted if you shut down the docker containers. The second line ensures that the TLS certificates needed to connect to the docker daemon are available to the Jenkins agent. The last line maps the home directory of the Jenkins agent to a specified folder on the host machine. You will need to replace <your home path> with the path to the folder you want to connect it to. For example, for me the path to the home folder on my windows machine is C:/Users/EHERMAN91, so this line will become “- C:/Users/EHERMAN91:/home”. By doing this you can access any repository located in this folder to build Jenkins jobs for. This is very convenient if you want to test some things without having to push to a GitHub or Bitbucket repository each time.
environment: - DOCKER_HOST=tcp://docker:2376 - DOCKER_CERT_PATH=/certs/client - DOCKER_TLS_VERIFY=1
These first environment line sets the docker host of the Jenkins agent to our dind image. This image has the alias “docker” for the Jenkins network we created and is exposed on port 2376. We will see these configurations below. The other environment variables are also needed to ensure the dind container and the Jenkins agent can work together.
All configurations for the docker in docker container are defined in this block.
As before, the container name is specified.
Instead of using a docker file, this container is built from a pre-existing image that is registered in a container registry. This image is specifically used to run docker inside another docker container.
Privileged mode is required to run docker in docker properly. This grants the dind contained root capabilities to all devices on the host (in this case the Jenkins agent).
Ensures that the containers are restarted if killed by unexpected behaviour.
networks: jenkins: aliases: - docker
This connects the dind image to the jenkins network and gives the image the alias “docker” for this network. As discussed before, this alias is used to set the dind image as DOCKER_HOST in the Jenkins agent container.
ports: - 2376:2376 - 3100:3100
Port 2376 is exposed so the Jenkins agent can connect to the docker daemon. I exposed port 3100 because I was running an application from inside a docker container inside a Jenkins pipeline that I wanted to access from my local browser. You can remove this if you are not planning to do something like that.
This makes sure you can access the docker container with a terminal.
volumes: - jenkins-data:/var/jenkins_home - jenkins-docker-certs:/certs/client
The first line ensures that docker containers controlled by the docker daemon in this container can access the data from Jenkins. The second line makes sure that the TLS certificates needed to connect to the docker daemon are available in the Jenkins-docker-certs volume.
environment: - DOCKER_TLS_CERTDIR=/certs
This enables the use of TLS in the docker server. This is recommended because of the privileged mode used. This is also the reason the TLS certificates are needed for everything to work.
Using the docker containers
Run “docker compose up” in a terminal in the same folder as where the docker-compose.yml file is located. If you don’t want to see the output in the command line you can run “docker compose up -d” (-d means detached). Once everything is running, you can go to localhost:8080 to access the Jenkins UI. The first time accessing the Jenkins agent will ask for the Administrator password. You can find this in the command line output. If your running in detached mode, you can use “docker logs jenkins-agent” to see the output.
The output should look something like this:
After submitting the administrator password you can install suggested plugins and create the first admin user. You will need this user to log back into Jenkins if you restart the docker containers. Now you are ready to create your first job!
To shut everything down run “docker compose down” or use ctrl + c if you are not running in detached mode.
The Dockerfile complete:
FROM jenkins/jenkins:2.277.1-lts-jdk11 USER root RUN apt-get update && apt-get install -y apt-transport-https \ ca-certificates curl gnupg2 \ software-properties-common RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - RUN apt-key fingerprint 0EBFCD88 RUN add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/debian \ $(lsb_release -cs) stable" RUN apt-get update && apt-get install -y docker-ce-cli RUN apt-get update && apt-get install -y wget RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - RUN echo "deb http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google.list RUN apt-get update RUN apt-get install -y google-chrome-stable xvfb ENV CHROME_BIN='/usr/bin/google-chrome-stable' USER jenkins
The docker-compose.yml file complete:
version: "3.1" services: jenkins-agent: container_name: jenkins-agent build: . restart: always networks: - jenkins ports: - 8080:8080 - 50000:50000 tty: true volumes: - jenkins-data:/var/jenkins_home - jenkins-docker-certs:/certs/client:ro - C:/Users/EHERMAN91:/home environment: - DOCKER_HOST=tcp://docker:2376 - DOCKER_CERT_PATH=/certs/client - DOCKER_TLS_VERIFY=1 dind: container_name: docker-for-jenkins image: docker:dind privileged: true restart: always networks: jenkins: aliases: - docker ports: - 2376:2376 - 3100:3100 tty: true volumes: - jenkins-data:/var/jenkins_home - jenkins-docker-certs:/certs/client environment: - DOCKER_TLS_CERTDIR=/certs networks: jenkins: volumes: jenkins-data: jenkins-docker-certs:
The files can also be found in this repository: https://github.com/EmmyHermans/Jenkins-SIG. Additionally, some exercises to practice with Jenkins can be found here.