Jenkins - Docker-in-Docker


Published: April 30, 2021 Author: Saad Ali

WARNING! Following this article, improvise if necessary. Your environment may be different than mine. I am not responsible if you screw up!

This tutorial helps set up a local Jenkins environment for running Jenkins jobs under Docker containers. I essentially consider this environment to be used for testing. For production, you might want to use a different strategy like installing Jenkins on the host system and run jobs under isolated container environments.

Install Docker

You can use the official guide to install Docker. And make sure (and this is the most important thing) that you add your user to the docker group:

usermod -a -G docker your_username

Docker Users and Groups

Running the Jenkins container as root user seems easy enough. But that's considered a bad practice. I am not going to dive into the details of why and make this blog post even longer. I'd rather have you do some reading about it. You'll first need to read Marc Campbell's Medium post - Understanding how uid and gid work in Docker containers. Do also read Processes In Containers Should Not Run As Root which is also written by Marc.


The Real Deal

In the container, Jenkins user ID and group ID are set to 1000. The user ID as well as Docker group ID in the container, need to match on the host. This will allow Jenkins (with UID 1000) to create containers similar to how it happens on the host. It also requires Docker to be installed to run and terminate job containers via /var/run/docker.sock (volume mounted via the docker run command in the container). The resulting Jenkins job containers are created on the host and are removed once the job completes. This is what results in a Docker-in-Docker phenomenon.

In addition to these host-to-container mappings, you need to install Docker and Docker Pipeline plugins to Jenkins.

I wrote a Dockerfile and a GitHub Actions workflow on GitHub under NIXKnight/Docker-Jenkins that builds and pushes the Jenkins image for LTS releases to DockerHub and Github registries. The Dockerfile installs Docker and its requirements and takes care of maintaining user and Docker group IDs. You can read the usage instructions on GitHub.


Running a Pipeline Job with Docker Agent

Once the plugin is installed, running a pipeline with a Docker agent is simple. You need to use the following declarative pipeline script under the Pipeline script definition of a Jenkins pipeline:

pipeline {
  agent {
    docker {
      image "bash:5.1.4"
    }
  }
  stages {
    stage('Donkey Work') {
      steps {
        sh '''
        if grep -sq 'docker' /proc/1/cgroup ; then
          echo -e "I'm running in a docker container."
        fi
        '''
      }
    }
  }
}

And this will yields the following output:

Started by user admin
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/jenkins_home/workspace/Docker_Agent_Test
[Pipeline] {
[Pipeline] isUnix
[Pipeline] sh
+ docker inspect -f . bash:5.1.4

Error: No such object: bash:5.1.4
[Pipeline] isUnix
[Pipeline] sh
+ docker pull bash:5.1.4

5.1.4: Pulling from library/bash
339de151aab4: Pulling fs layer
94a52eec71b5: Pulling fs layer
15ab8d50c870: Pulling fs layer
15ab8d50c870: Verifying Checksum
15ab8d50c870: Download complete
339de151aab4: Verifying Checksum
339de151aab4: Download complete
94a52eec71b5: Verifying Checksum
94a52eec71b5: Download complete
339de151aab4: Pull complete
94a52eec71b5: Pull complete
15ab8d50c870: Pull complete
Digest: sha256:c523c636b722339f41b6a431b44588ab2f762c5de5ec3bd7964420ff982fb1d9
Status: Downloaded newer image for bash:5.1.4
docker.io/library/bash:5.1.4

[Pipeline] withDockerContainer
Jenkins seems to be running inside container 63bb7c77c027bb9a19c853adaf619b32af52738174f30ced3098b350cf2eb7d2
$ docker run -t -d -u 1000:1000 -w /var/jenkins_home/workspace/Docker_Agent_Test --volumes-from 63bb7c77c027bb9a19c853adaf619b32af52738174f30ced3098b350cf2eb7d2 -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** -e ******** bash:5.1.4 cat

$ docker top 5bc6eacde3b9aca4ad7440deb40765e627445a08b9101e898493a41c4c3884fe -eo pid,comm
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Donkey Work)
[Pipeline] sh
+ grep -sq docker /proc/1/cgroup
+ echo -e 'I'"'"'m running in a docker container.'
I'm running in a docker container.
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
$ docker stop --time=1 5bc6eacde3b9aca4ad7440deb40765e627445a08b9101e898493a41c4c3884fe

$ docker rm -f 5bc6eacde3b9aca4ad7440deb40765e627445a08b9101e898493a41c4c3884fe

[Pipeline] // withDockerContainer
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

If the Docker image doesn't exist, it will pull it from the source you define.

This setup should be sufficient for you to test the pipelines that you deploy in production.


Share

Tagged as: Linux Jenkins Docker Containers