How to run Jenkins with Chrome from a Docker container

Emmy Hermans

In this blog I will show you how to run a Jenkins agent that can use chrome from a docker container. The Dockerfile and docker-compose.yml file are explained in this article.

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.

The Dockerfile

FROM jenkins/jenkins:2.277.1-lts-jdk11

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.

USER root

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.

USER jenkins

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.

version: "3.1"

The line above defines the version of the docker compose file format.

networks:
  jenkins:

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.

services:

In this block the two services will be defined: the Jenkins agent and the docker in docker (dind).

Jenkins-agent

  jenkins-agent:

All settings for the Jenkins agent are defined in this block.

    container_name: jenkins-agent

The image name, if not set docker will generate a (far less descriptive) name for you.  

    build: .

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).

    restart: always

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.

    tty: true

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.

Docker-for-jenkins

  dind:

All configurations for the docker in docker container are defined in this block.

    container_name: docker-for-jenkins

As before, the container name is specified.

    image: docker:dind

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: true

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).

    restart: always

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.

    tty: true

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:

Command line output for docker compose

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.

Complete files

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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.