Running Docker inside a Docker container (often called Docker-in-Docker or DinD) is a common requirement in CI/CD pipelines, especially when you need to build Docker images as part of your Jenkins jobs. However, this setup requires configuration to work properly and securely. In this post, we’ll explore how to set up and configure Docker-in-Docker in Jenkins pipelines.

The Challenge Link to heading

When running Docker commands inside a Jenkins pipeline that’s already running in a Docker container, you might encounter the following error:

ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

This error occurs because the container running your Jenkins job doesn’t have access to the Docker daemon on the host machine.

The Solution Link to heading

To enable Docker-in-Docker functionality, we need to:

  1. Mount the Docker socket from the host
  2. Add the container to the Docker group

Here’s how to implement this in your Jenkins pipeline:

1. Basic Jenkinsfile Configuration Link to heading

pipeline {
    agent {
        docker {
            image 'python:3.11-slim'
            label 'build'
        }
    }
    stages {
        stage('Build') {
            steps {
                sh 'docker build . -t my-app:local'
            }
        }
    }
}

With this Jenkinsfile, you will get the following error:

[Pipeline] sh
+ docker build . -t stem-ms-test:local
ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

2. Dynamic Docker Arguments Link to heading

In order to determine the correct Docker arguments, we need to know the group ID of the Docker socket. This must be done in the host machine, not the container. For more flexibility and security, you can create a utility function to determine the correct Docker arguments:

// jenkins-shared-library/vars/getDockerArgs.groovy
String call(String nodeLabel = 'build') {
    String groupId
    String grpIdCmd = 'stat -c %g /var/run/docker.sock'

    node(nodeLabel) {
        groupId = sh(script: grpIdCmd, returnStdout: true).trim()
    }

    return "--pull always --group-add=${groupId} -v /var/run/docker.sock:/var/run/docker.sock"
}

3. Using the Utility Function Link to heading

@Library('jenkins-shared-library@master') _

pipeline {
    agent {
        docker {
            image 'python:3.11-slim'
            args getDockerArgs()
            label 'build'
        }
    }
    stages {
        stage('Build') {
            steps {
                sh 'docker build . -t my-app:local'
            }
        }
    }
}

Now, you’re able to build the Docker image in the container:

[Pipeline] sh
+ docker build . -t stem-ms-test:local
#0 building with "default" instance using docker driver

#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 992B done
#1 DONE 0.0s
...
#16 DONE 3.9s

How It Works Link to heading

  1. Docker Socket Mounting:

    • -v /var/run/docker.sock:/var/run/docker.sock: Mounts the host’s Docker socket into the container
    • This allows the container to communicate with the host’s Docker daemon
  2. Group Access:

    • --group-add=${groupId}: Adds the container to the Docker group
    • The group ID is dynamically determined by checking the Docker socket’s group
    • This ensures proper permissions to use the Docker socket