How to Run Jenkins in Docker Containers

How to Run Jenkins in Docker Containers

Before I containerized Jenkins, upgrading it meant a nervous SSH session into a VM, crossing my fingers that the apt upgrade wouldn’t break some plugin dependency. Now Jenkins upgrades are a one-line docker pull and a container restart, with rollback just as easy. Running Jenkins in Docker isn’t just a convenience thing – it fundamentally changes how safely you can experiment, upgrade, and reproduce your CI environment. Here’s the complete setup.

Why Run Jenkins in Docker

  • Reproducibility – your Jenkins environment is defined in a Dockerfile, not built up manually over years through the UI.
  • Easy upgrades and rollbacks – swap the image tag, and if something breaks, roll back to the previous tag instantly.
  • Isolation – Jenkins and its dependencies don’t pollute the host OS, and you can run multiple Jenkins instances side by side for testing.
  • Portability – the exact same image runs identically on your laptop, a VM, or inside Kubernetes.

Jenkins-in-Docker Architecture

There are two distinct things to containerize, and it’s important not to conflate them:

  1. The Jenkins controller – the web UI, job configs, and orchestration logic, running as a long-lived container with persistent storage.
  2. Build agents – ephemeral containers spun up to actually execute your builds, which can be plain Docker containers, Docker-in-Docker, or (as covered in the Kubernetes article) dynamic pods.

Step 1: Run the Jenkins Controller with Docker

The simplest starting point:

docker volume create jenkins_home

docker run -d \
  --name jenkins \
  -p 8080:8080 \
  -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  jenkins/jenkins:lts-jdk17
  • Port 8080 serves the web UI.
  • Port 50000 is used for JNLP agent connections.
  • The named volume jenkins_home persists all Jenkins state (jobs, plugins, config) across container restarts and image upgrades.

Grab the initial admin password:

docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

Step 2: Build a Custom Jenkins Image

Rather than installing plugins by hand every time, bake them into a custom image so setup is fully reproducible:

FROM jenkins/jenkins:lts-jdk17

USER root

# Install Docker CLI so Jenkins can talk to the host Docker daemon
RUN apt-get update && apt-get install -y \
    apt-transport-https ca-certificates curl gnupg lsb-release \
    && curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
    && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) 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

COPY plugins.txt /usr/share/jenkins/ref/plugins.txt
RUN jenkins-plugin-cli --plugin-file /usr/share/jenkins/ref/plugins.txt

COPY jenkins.yaml /var/jenkins_home/casc_configs/jenkins.yaml
ENV CASC_JENKINS_CONFIG=/var/jenkins_home/casc_configs/jenkins.yaml

Build and run it:

docker build -t mycompany/jenkins:1.0 .
docker run -d --name jenkins -p 8080:8080 -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  mycompany/jenkins:1.0

Step 3: Let Jenkins Build and Run Docker Images (Docker-in-Docker vs Socket Mounting)

There are two common approaches for letting pipelines run docker build:

Option A: Mount the host Docker socket (simplest, but gives the container root-equivalent access to the host):

-v /var/run/docker.sock:/var/run/docker.sock

Option B: Docker-in-Docker (dind) – run an isolated Docker daemon inside its own container, which is safer for multi-tenant setups but has more overhead:

# docker-compose.yml
services:
  jenkins:
    image: mycompany/jenkins:1.0
    ports:
      - "8080:8080"
      - "50000:50000"
    volumes:
      - jenkins_home:/var/jenkins_home
    environment:
      - DOCKER_HOST=tcp://docker:2376
      - DOCKER_CERT_PATH=/certs/client
      - DOCKER_TLS_VERIFY=1
    volumes:
      - jenkins-docker-certs:/certs/client

  docker:
    image: docker:24-dind
    privileged: true
    environment:
      - DOCKER_TLS_CERTDIR=/certs
    volumes:
      - jenkins-docker-certs:/certs/client
      - jenkins-data:/var/jenkins_home

volumes:
  jenkins_home:
  jenkins-docker-certs:
  jenkins-data:

For most single-tenant internal CI setups, socket mounting is simpler and perfectly adequate; reserve dind for cases where build isolation is a hard requirement.

Step 4: Configure Jenkins as Code

Instead of clicking through setup screens, define your controller config declaratively:

# jenkins.yaml
jenkins:
  systemMessage: "Jenkins configured via JCasC"
  numExecutors: 2
  securityRealm:
    local:
      allowsSignup: false
      users:
        - id: admin
          password: ${JENKINS_ADMIN_PASSWORD}
  authorizationStrategy:
    loggedInUsersCanDoAnything:
      allowAnonymousRead: false

unclassified:
  location:
    url: http://jenkins.mycompany.com/

Pass JENKINS_ADMIN_PASSWORD as an environment variable or Docker secret at container start rather than hard-coding it.

Step 5: Use Docker Agents Directly in a Jenkinsfile

You don’t need a full Kubernetes cluster to get ephemeral, containerized build environments – Jenkins can spin up a Docker container per stage using the docker agent directive, provided the controller can reach the Docker daemon:

pipeline {
    agent none

    stages {
        stage('Build') {
            agent {
                docker {
                    image 'maven:3.9-eclipse-temurin-17'
                    args '-v $HOME/.m2:/root/.m2'
                }
            }
            steps {
                sh 'mvn -B clean package'
            }
        }

        stage('Build Image') {
            agent any
            steps {
                sh 'docker build -t myapp:${BUILD_NUMBER} .'
                sh 'docker push myregistry.com/myapp:${BUILD_NUMBER}'
            }
        }
    }
}

Each stage gets a clean, disposable container, keeping your build environment consistent no matter which Jenkins agent happens to pick it up.

Backing Up and Restoring Jenkins Data

Because everything lives in the jenkins_home volume, backups are straightforward:

docker run --rm -v jenkins_home:/var/jenkins_home -v $(pwd):/backup \
  alpine tar czf /backup/jenkins_backup_$(date +%F).tar.gz -C /var/jenkins_home .

Restore by extracting that archive back into a fresh volume before starting the container.

Integrating with the Wider Toolchain

  • Git/GitHub – webhook-triggered builds work identically whether Jenkins is containerized or not; just ensure the container’s exposed port is reachable from GitHub.
  • Kubernetes – the same image you build here is exactly what you’d deploy to a cluster (see the companion article on running Jenkins on Kubernetes) – Docker Compose is a great local/staging step before that jump.
  • Terraform/Ansible – provision the host VM and Docker installation itself via Terraform, then use Ansible or a simple docker run script for the Jenkins container lifecycle.
  • Monitoring tools – expose the Prometheus plugin’s /prometheus metrics endpoint and scrape it from a containerized Prometheus/Grafana stack running alongside Jenkins.

Monitoring and Troubleshooting

  • Permission denied on docker.sock – the jenkins user inside the container needs to be in a group matching the host’s Docker group GID; add usermod -aG docker jenkins in your Dockerfile or match GIDs explicitly.
  • Jenkins losing data after container recreation – almost always means the volume wasn’t mounted correctly; double-check -v jenkins_home:/var/jenkins_home is present every time you recreate the container.
  • Out of disk space – Jenkins workspaces and Docker images both accumulate quickly; schedule docker system prune and Jenkins workspace cleanup (via the Workspace Cleanup plugin) regularly.
  • Container restarts losing plugin installs done via UI – if you’re baking plugins into the image, UI-installed plugins not in plugins.txt will vanish on the next rebuild; that’s expected behavior, add them to plugins.txt instead.

Security Best Practices

  • Avoid mounting the Docker socket into the Jenkins container in genuinely multi-tenant environments – it effectively grants root on the host to anyone with build-script access.
  • Run the container as a non-root user where possible – the official jenkins/jenkins image already does this by default.
  • Keep the base image patched; rebuild and redeploy on a schedule rather than running the same image for a year.
  • Store secrets via Docker secrets or Jenkins Credentials, never as plain environment variables baked into the image.

Best Practices

  • Treat the Jenkins container as disposable and the volume as the source of truth – never store anything important only inside the container’s writable layer.
  • Version-control your Dockerfile, plugins.txt, and JCasC YAML together so the entire controller setup can be rebuilt from Git.
  • Use Docker Compose (or a small set of docker run scripts) rather than manual ad hoc commands, so the setup is repeatable across environments.
  • Tag your custom Jenkins images with meaningful versions, not just latest, so rollbacks are unambiguous.

FAQs

Should the Jenkins controller itself run builds, or only agents? Only agents, ideally – keep the controller container lightweight and dedicate all execution to ephemeral agent containers, whether Docker or Kubernetes-based.

Can I run Jenkins in Docker on Windows? Yes, using Windows containers or WSL2-backed Docker Desktop, though most production Jenkins-in-Docker setups target Linux containers for simplicity.

Is Docker Compose enough for production, or do I need Kubernetes? Docker Compose is fine for small teams or single-host setups; once you need multi-node scaling, self-healing, or elastic agents, Kubernetes becomes the better fit.

How do I move from a bare-metal Jenkins install to Docker? Copy your existing $JENKINS_HOME directory into the new named volume before starting the containerized version – Jenkins will pick up all existing jobs, credentials, and config.

Does containerizing Jenkins slow builds down? Not meaningfully – the overhead is in container startup for ephemeral agents (usually a few seconds), which is a fair trade for consistency and isolation.

Summary

Running Jenkins in Docker gives you a controller that’s easy to back up, upgrade, and reproduce, plus the option to run every build in a clean, disposable container. Start with the official jenkins/jenkins image and a persistent volume, layer in a custom Dockerfile with your plugins baked in via plugins.txt, configure the controller declaratively with JCasC, and use the docker agent directive in your Jenkinsfiles for consistent, isolated build environments. It’s a natural stepping stone toward Kubernetes if your scaling needs grow, but stands entirely on its own for small-to-mid-sized teams.

References

  • Official Jenkins Docker image documentation: https://github.com/jenkinsci/docker
  • Jenkins Configuration as Code plugin: https://plugins.jenkins.io/configuration-as-code/
  • Docker Pipeline Plugin: https://plugins.jenkins.io/docker-workflow/
  • Docker-in-Docker official image: https://hub.docker.com/_/docker
  • Jenkins Pipeline Syntax reference: https://www.jenkins.io/doc/book/pipeline/syntax/
Total
1
Shares

Leave a Reply

Previous Post
How to Set Up Jenkins for PHP Projects

How to Set Up Jenkins for PHP Projects

Next Post
How to Create and Manage Jenkins Plugins

How to Create and Manage Jenkins Plugins

Related Posts