How to Use Docker in Jenkins: Container Builds and Docker Agents
"Adding Docker to Jenkins" can mean three different things:
- Running pipeline stages inside a container, so the build toolchain comes from an image instead of being installed on the agent. This is the Docker Pipeline plugin.
- Building and pushing images from a pipeline.
- Starting whole Jenkins agents as containers on demand and throwing them away after the build. This is the Docker plugin and its "Docker cloud".
All three need the same thing first: an agent that can talk to a Docker daemon.
Step 1: give the agent access to Docker
Jenkins on a VM. Install Docker Engine on every agent that will run Docker builds, then let the jenkins user reach the socket:
sudo usermod -aG docker jenkins
sudo systemctl restart jenkins # or restart the agent process
Group changes only apply to new processes, so the restart is required. Keep in mind that the docker group is root-equivalent on that machine, and so is any job that can run docker. Run builds on agents, not on the controller (set the controller's executors to 0).
Jenkins running in a container. The Jenkins image has no Docker CLI and no daemon. The setup in the Jenkins documentation runs a separate docker:dind container with TLS and points Jenkins at it:
docker network create jenkins
docker run --name jenkins-docker --detach --privileged \
--network jenkins --network-alias docker \
--env DOCKER_TLS_CERTDIR=/certs \
--volume jenkins-docker-certs:/certs/client \
--volume jenkins-data:/var/jenkins_home \
docker:dind --storage-driver overlay2
Build a Jenkins image that includes the Docker CLI and the plugins:
FROM jenkins/jenkins:lts-jdk21
USER root
RUN apt-get update && apt-get install -y ca-certificates curl && \
install -m 0755 -d /etc/apt/keyrings && \
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
> /etc/apt/sources.list.d/docker.list && \
apt-get update && apt-get install -y docker-ce-cli && rm -rf /var/lib/apt/lists/*
USER jenkins
RUN jenkins-plugin-cli --plugins "docker-workflow docker-plugin"
docker build -t my-jenkins .
docker run --name jenkins --detach --restart=on-failure \
--network jenkins \
--env DOCKER_HOST=tcp://docker:2376 \
--env DOCKER_CERT_PATH=/certs/client --env DOCKER_TLS_VERIFY=1 \
--publish 8080:8080 --publish 50000:50000 \
--volume jenkins-data:/var/jenkins_home \
--volume jenkins-docker-certs:/certs/client:ro \
my-jenkins
The shortcut you will see in many guides is mounting the host socket with -v /var/run/docker.sock:/var/run/docker.sock. It works, but every job then has root on the host. Use it only on a machine dedicated to CI.
Step 2: install the plugins
In Manage Jenkins > Plugins, install:
- Docker Pipeline (
docker-workflow):agent { docker { ... } },agent { dockerfile true }and thedocker.build()/docker.withRegistry()steps. - Docker (
docker-plugin): only if you want Jenkins to start agents as containers (step 5).
Step 3: run stages inside a container
pipeline {
agent {
docker {
image 'maven:3.9-eclipse-temurin-21'
args '-v $HOME/.m2:/root/.m2'
}
}
stages {
stage('Build') {
steps {
sh 'mvn -B package'
}
}
}
}
Jenkins pulls the image, starts it on an agent with Docker, mounts the workspace and runs the sh steps inside. The container runs with the agent user's UID, so tools in the image must not assume they are root. The args line mounts a Maven cache so dependencies survive between builds.
You can also pick an image per stage. reuseNode true keeps the stage on the same agent and workspace as the rest of the pipeline:
pipeline {
agent { label 'docker' }
stages {
stage('Test') {
agent {
docker {
image 'node:22-alpine'
reuseNode true
}
}
steps {
sh 'npm ci && npm test'
}
}
}
}
If the repository already has a Dockerfile for its build environment, agent { dockerfile true } builds it and runs the pipeline inside the result.
Step 4: build and push an image
Plain docker commands in sh steps work on any agent with Docker access:
pipeline {
agent { label 'docker' }
environment {
IMAGE = "registry.example.com/my-app:${env.BUILD_NUMBER}"
}
stages {
stage('Build image') {
steps {
sh 'docker build -t "$IMAGE" .'
}
}
stage('Push') {
steps {
withCredentials([usernamePassword(credentialsId: 'registry-creds',
usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')]) {
sh 'echo "$REG_PASS" | docker login registry.example.com -u "$REG_USER" --password-stdin'
sh 'docker push "$IMAGE"'
}
}
}
}
post {
always {
sh 'docker logout registry.example.com || true'
}
}
}
Note the single quotes around the sh commands. With double quotes, Groovy interpolates the secret into the command string before the shell sees it, and Jenkins warns about exactly that.
The Docker Pipeline plugin offers the same thing as a DSL:
script {
docker.withRegistry('https://registry.example.com', 'registry-creds') {
def img = docker.build("my-app:${env.BUILD_NUMBER}")
img.push()
img.push('latest')
}
}
Step 5: Docker agents from a Docker cloud
With the Docker plugin, Jenkins starts a fresh container for each build and removes it afterwards. No long-lived agents to patch, no leftovers between builds.
- Go to Manage Jenkins > Clouds > New cloud, choose Docker.
- Docker Host URI:
unix:///var/run/docker.sockfor the local daemon, ortcp://docker-host.example.com:2376with a client certificate in Server credentials. Never expose an unauthenticated daemon on port 2375. Click Test Connection and enable the cloud. - Add an agent template:
- Labels:
docker-agent - Docker Image:
jenkins/agent:latest-jdk21 - Remote File System Root:
/home/jenkins/agent - Connect method: Attach Docker container (works with
jenkins/agent). Connect with JNLP needsjenkins/inbound-agentand a controller URL the container can reach. Connect with SSH needsjenkins/ssh-agent.
- Labels:
- Use the label in a pipeline:
agent { label 'docker-agent' }.
For real builds, extend the agent image with your tools:
FROM jenkins/agent:latest-jdk21
USER root
RUN apt-get update && apt-get install -y --no-install-recommends git make \
&& rm -rf /var/lib/apt/lists/*
USER jenkins
Which approach to use
- One Jenkins with a few agents and varied toolchains: Docker Pipeline with
agent { docker }. - A pool of identical, disposable agents: Docker cloud.
- Jenkins already on Kubernetes: the Kubernetes plugin, which runs agents as pods, fits better than either.
Troubleshooting
permission denied ... /var/run/docker.sock: thejenkinsuser is not in thedockergroup, or Jenkins was not restarted after adding it.docker: not found: the agent (or the Jenkins image) has no Docker CLI.- Inbound agents never connect: the container cannot reach the controller URL or port 50000.
Quick reference
sudo usermod -aG docker jenkins && sudo systemctl restart jenkins
Plugins: docker-workflow (pipelines), docker-plugin (Docker cloud)
agent { docker { image 'maven:3.9-eclipse-temurin-21' } }
agent { dockerfile true }
docker.withRegistry(url, credentialsId) { docker.build(name).push() }
Manage Jenkins > Clouds > New cloud > Docker
