Jenkins Declarative Pipelines can build Docker images, but the magic isn’t in the pipeline syntax itself; it’s in how Jenkins orchestrates Docker commands and manages the build environment.

Here’s a typical pipeline that builds a Docker image:

pipeline {
    agent any

    stages {
        stage('Checkout') {
            steps {
                git 'https://github.com/your-username/your-repo.git'
            }
        }
        stage('Build Docker Image') {
            steps {
                script {
                    def imageName = "my-app:${env.BUILD_NUMBER}"
                    sh "docker build -t ${imageName} ."
                    sh "docker tag ${imageName} my-app:latest"
                }
            }
        }
        stage('Push Docker Image') {
            steps {
                script {
                    withCredentials([usernamePassword(credentialsId: 'docker-hub-credentials', usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD')]) {
                        sh "echo ${DOCKER_PASSWORD} | docker login -u ${DOCKER_USERNAME} --password-stdin"
                        sh "docker push my-app:latest"
                        sh "docker push my-app:${env.BUILD_NUMBER}"
                    }
                }
            }
        }
    }
}

This pipeline checks out code, builds a Docker image tagged with the Jenkins build number and latest, logs into Docker Hub, and then pushes both tags.

The core problem this solves is automating the creation and distribution of containerized applications. Instead of manually running docker build and docker push on a developer’s machine or a dedicated build server, Jenkins handles it consistently, every time code is committed or a release is triggered. This ensures that the image built in CI is exactly what gets deployed.

Internally, when Jenkins executes sh "docker build ..." or sh "docker push ...", it’s essentially running these Docker CLI commands on the agent where the pipeline is executing. The agent needs to have Docker installed and configured correctly. If you’re using a Jenkins agent that’s a Docker container itself, you’ll often need to mount the Docker socket (/var/run/docker.sock) into that agent container, or use a Docker-in-Docker (dind) setup. For cloud-based agents (like EC2 or Kubernetes), the agent needs appropriate permissions to interact with the Docker daemon. The withCredentials block is crucial for securely handling sensitive information like Docker registry credentials, preventing them from being hardcoded in the pipeline script.

The most surprising thing about building Docker images in Jenkins is how often people struggle with the agent’s Docker environment rather than the pipeline syntax. Many assume that because the pipeline is running, Docker will magically be available and functional. However, the agent’s ability to execute docker commands depends entirely on its own setup: is Docker installed? Is the user running the Jenkins agent process part of the docker group (or have equivalent permissions)? Is the Docker daemon running? For agents that are themselves Docker containers, how is the Docker daemon exposed to them? The sh step is just a shell execution; it doesn’t magically grant Docker capabilities.

The next hurdle you’ll likely face is managing multi-stage Docker builds efficiently within Jenkins, especially when dealing with large intermediate layers.

Want structured learning?

Take the full Jenkins course →