rtyler

Publishing containers with Jenkins Pipeline

Pulling Docker containers from Docker Hub doesn’t require any special handling or credentials, making it quite simple to consume Docker containers in a Jenkins Pipeline. In this blog post however, I’ll describe a simple pattern which I have been using to programmatically publish Docker containers to Docker Hub from a Jenkins Pipeline.

To the best of my current understanding, there are no environment variables or command line arguments available for specifying an authentication credential with Docker Hub. This has caused a number of the Pipelines I have seen to include a docker login invocation directly inside of a Jenkinsfile. I dislike this for a number of reasons, but generally it seems unnecessarily hackish. Fortunately, the Docker command line supports a handy environment variable DOCKER_CONFIG which allows a user to override the default path of ~/.docker/config.json to some place else on the filesystem. This combined with the credentials support Jenkins has out of the box, allows this problem to be easily solved without any additional plugins or hacks.

Prepare credentials file

On a systems administrators workstation, the credentials file can be prepared by running the following commands:

mkdir dot-docker
DOCKER_CONFIG=$PWD/dot-docker docker login
(cd dot-docker && zip -r docker-config.json.zip config.json)

This takes advantage of the DOCKER_CONFIG environment variable to create a brand new config.json with my desired authentication token. Once I have this zip file I can add the credential to Jenkins.

Add credentials to Jenkins

As an administrator, one can add credentials to a global scope in Jenkins, which the following screenshots demonstrate. Users may be allowed to add credentials on a per-Folder level as well, but in this example I’m simply adding credentials which are visible to all Pipelines in my Jenkins environment.

First I navigate from the home page to Credentials, then click on the (global) item under “Stores scoped to Jenkins”. From that page I click Add Credentials.

Adding credentials to Jenkins

From the “Add Credentials” page, I can then upload the config.json.zip I previously created. I will also give this credential a useful, human-understandable ID, to make it easier to reference in the future.

Configuring credentials to Jenkins

Once the credential has been saved, I can use it in my Pipeline.

Use credentials in Pipeline

This example uses Scripted Pipeline syntax, though it’s relatively trivial to use the Docker CLI from within Declarative Pipeline syntax to accomplish something similar.

In my Jenkins environments, I typically label agents which have a docker.sock available to Jenkins as “docker”. Since the Pipeline needs to first build the image, I’m assigning the Pipeline to a “docker” agent.

Once the Pipeline begins to execute, it will first build the container, then test it (typically using bats or shellcheck), and then publish the container to Docker Hub.

node('docker') {
    def container
    stage('Build') {
        container = docker.build('zerocool:latest')
    }
    stage('Test') {
        /* test the container */
    }
    stage('Publish') {
        withCredentials([zip(credentialsId: 'docker-config',
                                  variable: 'DOCKER_CONFIG')]) {
            echo 'Pushing to Docker Hub'
            container.push()
        }
    }
}

While Declarative Pipeline syntax doesn’t yet support the use of Zip credentials in the environment {} directive (JENKINS-50021), but the withCredentials step can be used with Declarative syntax.

Using the above steps, it’s easy to start building more and more advanced continuous delivery pipelines for Docker containers, preventing rogue developers (like myself) from yolo-pushing containers straight from their laptops to Docker Hub :)