Ravindra BagaleCourses & study guides मराठी

Chapter 27: Jenkins Agents

27.4 Docker-in-Docker is risky

Why: the problem

Think of a fintech company like Paytm. A developer wants Jenkins to build Docker images. The quick answer on many blogs is: mount /var/run/docker.sock into the build container, or run a docker:dind container with --privileged. Both work. Both are dangerous. Whoever can talk to the Docker socket can start a container that mounts the host root disk and become root on the agent. A privileged container can do almost anything on the host kernel. If a pull request from an outsider runs on that agent, the outsider owns the machine and can steal every secret on it. So security reviews reject these patterns on shared agents. Safe options exist: build images with Kaniko or Buildah, which need no Docker daemon, use rootless Docker, or give a dedicated, short-lived agent to trusted jobs only. In this lesson you will see the risk with your own eyes on a lab VM, and then build the same image safely in three ways.

What we will build

On a throwaway lab VM you will show how docker.sock gives root. Then you will build and push one image three safe ways: Kaniko in a Kubernetes pod agent, Buildah rootless on a normal agent, and rootless Docker for the jenkins user. You will finish with a short review checklist for agents that build images.

What: every term in plain English

  • Docker socket — The file /var/run/docker.sock. Any program that can write to it controls Docker, which runs as root.
  • docker group — Linux group that can use the socket. Being in it is almost the same as being root.
  • Docker-in-Docker (DinD) — Running a full Docker daemon inside a container. Needs --privileged.
  • --privileged — Gives a container nearly all host powers. Unsafe on shared machines.
  • Kaniko — A Google tool that builds images from a Dockerfile inside a normal container, with no daemon.
  • Buildah — A Red Hat tool that builds images without a daemon, and can run rootless.
  • Rootless Docker — Docker daemon running as a normal user. A break-out gives only that user, not root.
  • Trusted job — A job that runs only code from your own protected branches, not outside pull requests.
Show the risk on a lab VM only Find risky mounts in Jenkinsfiles Create a registry secret for Kaniko Install Buildah on an agent Set up rootless Docker for jenkins Point the client at it Review rule
Risky build versus safe build, top to bottom

How: step by step

  1. Show the risk on a lab VM only

    docker run --rm -v /var/run/docker.sock:/var/run/docker.sock docker:27-cli docker run --rm -v /:/host alpine cat /host/etc/shadow | head -2
    

    What it does: A container with the socket starts another container that reads root-only host files.

    Output

    root:*:19800:0:99999:7:::
    daemon:*:19800:0:99999:7:::
    
  2. Find risky mounts in Jenkinsfiles

    grep -rn "docker.sock\|--privileged\|dind" --include=Jenkinsfile .
    

    What it does: Lists jobs that use the risky patterns.

    Output

    ./payments/Jenkinsfile:7: args "-v /var/run/docker.sock:/var/run/docker.sock"
    
  3. Create a registry secret for Kaniko

    kubectl -n jenkins create secret docker-registry regcred --docker-server=https://index.docker.io/v1/ --docker-username=ravilab --docker-password="$DOCKERHUB_TOKEN"
    

    What it does: Kaniko reads this to push. The token comes from an env var, not typed in Git.

    Output

    secret/regcred created
    
  4. Install Buildah on an agent

    sudo apt-get install -y buildah uidmap && buildah --version
    

    What it does: Buildah with user namespace tools for rootless use.

    Output

    buildah version 1.33.7
    
  5. Set up rootless Docker for jenkins

    sudo apt-get install -y docker-ce-rootless-extras uidmap && sudo -iu jenkins dockerd-rootless-setuptool.sh install
    

    What it does: A Docker daemon that runs as the jenkins user.

    Output

    [INFO] Installed docker.service successfully.
    
  6. Point the client at it

    export DOCKER_HOST=unix:///run/user/1001/docker.sock && docker info --format "{{.SecurityOptions}}"
    

    What it does: The client talks to the user daemon, not the root one.

    Output

    [name=seccomp,profile=builtin name=rootless name=cgroupns]
    
  7. Review rule

    Shared agents: no docker.sock mounts, no --privileged. Image builds: Kaniko, Buildah or rootless.
    

    What it does: Write this in the team runbook.

    Output

    Rule added to runbook
    

Example 1: Build and push with Kaniko in a pod agent

Stack. Jenkins Kubernetes plugin, Kaniko, Docker Hub

Goal. Build and push with Kaniko in a pod agent.

Jenkinsfile

pipeline {
  agent {
    kubernetes {
      yaml """
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: kaniko
    image: gcr.io/kaniko-project/executor:v1.23.2-debug
    command: ['sleep']
    args: ['infinity']
    volumeMounts:
    - name: docker-config
      mountPath: /kaniko/.docker
  volumes:
  - name: docker-config
    secret:
      secretName: regcred
      items:
      - key: .dockerconfigjson
        path: config.json
"""
    }
  }
  stages {
    stage('Build and push') {
      steps {
        container('kaniko') {
          sh '/kaniko/executor --context "$WORKSPACE" --dockerfile Dockerfile --destination ravilab/shop-api:$BUILD_NUMBER --cache=true'
        }
      }
    }
  }
}

What each line does

  • kubernetes { yaml ... } — Kubernetes plugin starts a pod as the agent (lesson 27.5).
  • executor:v1.23.2-debug — The Kaniko image. The debug tag has a shell so sh steps work.
  • command sleep infinity — Keeps the container alive so Jenkins can run steps in it.
  • secret regcred to /kaniko/.docker/config.json — Kaniko reads registry login from here.
  • /kaniko/executor --context --destination — Builds the Dockerfile and pushes the image. No Docker daemon anywhere.
  • --cache=true — Reuses layers from the registry to build faster.
  • No privileged — The pod runs as a normal container.

Green output

[Pipeline] container
+ /kaniko/executor --context /home/jenkins/agent/workspace/shop-api --dockerfile Dockerfile --destination ravilab/shop-api:61 --cache=true
INFO[0002] Retrieving image manifest node:20-alpine
INFO[0011] Taking snapshot of full filesystem...
INFO[0019] Pushing image to ravilab/shop-api:61
INFO[0024] Pushed index.docker.io/ravilab/shop-api@sha256:2d4f...
Finished: SUCCESS

Expected result. The image was built and pushed from an ordinary pod. A security review accepts this.

Example 2: Build rootless with Buildah on a normal agent

Stack. Buildah rootless, Harbor registry, Jenkins

Goal. Build rootless with Buildah on a normal agent.

Jenkinsfile

pipeline {
  agent { label 'buildah' }
  environment { IMAGE = "registry.example.in/shop/api:${BUILD_NUMBER}" }
  stages {
    stage('Build') {
      steps { sh 'buildah bud --layers -t $IMAGE .' }
    }
    stage('Push') {
      steps {
        withCredentials([usernamePassword(credentialsId: 'harbor-robot', usernameVariable: 'U', passwordVariable: 'P')]) {
          sh 'echo "$P" | buildah login -u "$U" --password-stdin registry.example.in'
          sh 'buildah push $IMAGE'
        }
      }
    }
  }
  post { always { sh 'buildah rmi --prune || true' } }
}

What each line does

  • label 'buildah' — An agent with Buildah and no Docker group.
  • buildah bud --layers -t — Build using Dockerfile, keep layer cache, tag the image.
  • usernamePassword(...) — Harbor robot account from Jenkins credentials.
  • --password-stdin — Password goes through a pipe, not the command line.
  • buildah push — Uploads the image to the registry.
  • buildah rmi --prune — Removes unused images so the disk stays clean.

Green output

+ buildah bud --layers -t registry.example.in/shop/api:62 .
STEP 1/6: FROM node:20-alpine
STEP 6/6: CMD ["node","server.js"]
COMMIT registry.example.in/shop/api:62
+ buildah login -u robot$shop-ci --password-stdin registry.example.in
Login Succeeded!
+ buildah push registry.example.in/shop/api:62
Writing manifest to image destination
Finished: SUCCESS

Expected result. The jenkins user built and pushed an image without root and without any socket.

Example 3: Rootless Docker for teams that need docker commands

Stack. Rootless Docker, Ubuntu 22.04, Jenkins

Goal. Rootless Docker for teams that need docker commands.

Jenkinsfile

pipeline {
  agent { label 'rootless-docker' }
  environment { DOCKER_HOST = 'unix:///run/user/1001/docker.sock' }
  stages {
    stage('Who runs Docker') {
      steps { sh 'docker info --format "rootless={{.SecurityOptions}}"; id -u' }
    }
    stage('Build and test') {
      steps {
        sh 'docker build -t shop-api:ci .'
        sh 'docker run --rm shop-api:ci npm test'
      }
    }
  }
}

agent check

# on the agent, as an admin
id jenkins
# must NOT show the docker group
systemctl --user -M jenkins@ status docker --no-pager | head -3

What each line does

  • DOCKER_HOST unix:///run/user/1001/docker.sock — Use the rootless daemon of uid 1001, the jenkins user.
  • docker info ... rootless — Proves the daemon is rootless.
  • id -u — Shows 1001, not 0.
  • id jenkins — The jenkins user should not be in the docker group.
  • docker run --rm ... npm test — Runs tests in the image you just built.

Green output

+ docker info --format rootless={{.SecurityOptions}}
rootless=[name=seccomp,profile=builtin name=rootless name=cgroupns]
+ id -u
1001
+ docker build -t shop-api:ci .
Successfully tagged shop-api:ci
+ docker run --rm shop-api:ci npm test
Tests: 14 passed, 14 total
Finished: SUCCESS

Expected result. Developers keep normal docker commands, but a break-out gets only the jenkins user.

Verify: did it work?

  1. No socket in pod specs

    kubectl -n jenkins get pods -o yaml | grep -c docker.sock
    

    What it does: Count should be 0.

    Output

    0
    
  2. No privileged agents

    kubectl -n jenkins get pods -o jsonpath="{..securityContext.privileged}"
    

    What it does: Should print nothing or false.

    Output

    (no output)
    

When it is red 1

error checking push permissions -- make sure you entered the correct tag name, and that you are authenticated correctly

Cause. Kaniko has no valid registry config.

Fix, step by step

  1. Check the regcred secret exists in the jenkins namespace.
  2. Check it is mounted at /kaniko/.docker/config.json.
  3. Use an access token with write scope.

When it is red 2

Error: cannot setup namespace using newuidmap: exit status 1

Cause. Rootless Buildah needs subuid and subgid ranges for the user.

Fix, step by step

  1. sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 jenkins.
  2. Install uidmap.
  3. Log out and in again or restart the agent.

When it is red 3

Cannot connect to the Docker daemon at unix:///run/user/1001/docker.sock

Cause. The rootless daemon is not running for the jenkins user.

Fix, step by step

  1. sudo loginctl enable-linger jenkins.
  2. systemctl --user -M jenkins@ start docker.
  3. Check DOCKER_HOST matches the uid.

Cleanup

  1. Remove the lab socket test image

    docker image rm docker:27-cli alpine
    

    What it does: Cleans the risky demo images from the lab VM.

    Output

    Untagged: docker:27-cli
    
  2. Delete the Kaniko secret

    kubectl -n jenkins delete secret regcred
    

    What it does: Removes the lab registry secret.

    Output

    secret "regcred" deleted
    

Ravindra Bagale's Tip

docker.sock mount mhanje host chi root chavi deli. Kaniko kiva Buildah vapra. Lakshat theva!

Practice tasks

Easy Find risky jobs

Why: You must know where the risk is.

What to do: grep Jenkinsfiles for docker.sock.

How:
  1. Run grep.
  2. List the jobs.

Check: A list of risky jobs.

Medium Kaniko build

Why: Daemonless builds are review friendly.

What to do: Push one image with Kaniko.

How:
  1. Create regcred.
  2. Pod agent with Kaniko.

Check: Image appears in the registry.

Hard Rootless agent

Why: Some teams need docker commands.

What to do: Set up rootless Docker for jenkins.

How:
  1. Install rootless extras.
  2. enable-linger.
  3. Set DOCKER_HOST.

Check: docker info shows rootless.

Easy Say safe image builds without DinD in two lines

Why: If you can say it simply, you understand it. Juniors and interviewers want the short version first.

What to do: Write two short lines about safe image builds without DinD for a fresher who knows only Linux basics.

How:
  1. Write what problem it solves.
  2. Write the one command or menu you use for it.
  3. Read it aloud to a friend.

Check: A fresher understands it without asking a question.

Easy Start a fresh lab Jenkins

Why: Practice needs a throwaway Jenkins. If you break it, you delete it and start again.

What to do: Run Jenkins LTS in Docker with a named volume.

How:
  1. docker volume create jenkins_home
  2. docker run -d --name jenkins -p 8080:8080 -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts-jdk17
  3. docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

Check: The Jenkins unlock page opens on localhost:8080.

Medium Break it on purpose

Why: You must know the red message by face. During an outage there is no time to read slowly.

What to do: Cause one error from the "When it is red" part of this lesson and fix it.

How:
  1. Follow the error section.
  2. Copy the exact message into your notes.
  3. Write the fix in one line.

Check: You fixed it using only the steps, without searching the web.

Medium Write a one-page runbook

Why: At 2 a.m. nobody remembers where a file lives. A short page saves the on-call person.

What to do: Write a runbook page for this lesson.

How:
  1. Title, when to use, exact command, how to check, how to undo.

Check: A teammate can follow it alone.

Medium Repeat it on a cloud VM

Why: A laptop is not a server. Real Jenkins runs on EC2 or a company VM.

What to do: Do the main steps of this lesson again on a small Ubuntu 22.04 EC2 instance.

How:
  1. Launch t3.small with 20 GB disk.
  2. Open port 22 only to your IP.
  3. Repeat the steps and compare the output.

Check: Same green output on the VM as on your laptop.

Hard Explain it like an interview

Why: Interviewers want real stories, not definitions.

What to do: Record a 60 second answer about this lesson.

How:
  1. Say the problem, the command you ran, and one error you hit.

Check: Your answer has a real command and a real error message.

Hard Draw the flow top to bottom

Why: A drawing shows who talks to whom and in what order.

What to do: Draw this lesson as boxes from top to bottom.

How:
  1. Start at the developer push, go through the controller and agent, end at the result.

Check: Each box matches a real thing you saw on screen.

Homework: try at home

Try at home

At home, search all Jenkinsfiles in one of your repos for docker.sock, --privileged and dind. For each hit, write which safe method you would use instead: Kaniko, Buildah or rootless Docker. Rewrite one job and run it.

Recap

  • docker.sock access is root access.
  • DinD needs --privileged; avoid it on shared agents.
  • Use Kaniko or Buildah to build images without a daemon.
  • Use rootless Docker when docker commands are needed.
  • Run untrusted pull requests only on isolated, short-lived agents.

FAQ

Is DinD ever okay?

On a dedicated, short-lived VM for trusted jobs only, and after a security sign-off.

Is Kaniko slower?

A little at first. Use --cache=true with a cache repo.

Can Kaniko run docker run for tests?

No. It only builds. Run tests in a separate container stage.

What about Podman?

Podman also runs rootless and works much like Buildah.

Does BuildKit help?

BuildKit rootless mode is another safe option.

Interview questions

  1. Why do security teams reject docker.sock mounts in CI?

    The socket controls a root daemon. A job can start a container that mounts the host disk and take over the agent and its secrets.

  2. How do you build images in Jenkins on Kubernetes without privileged pods?

    Kaniko or Buildah in a pod agent, with registry credentials from a Kubernetes secret.

  3. What is the difference between DinD and Docker-outside-of-Docker?

    DinD runs a new daemon inside a privileged container; DooD mounts the host socket. Both give host-level power.

  4. How would you run outside pull requests safely?

    On isolated, ephemeral agents with no secrets and no socket, after approval.

  5. What is rootless Docker?

    The daemon and containers run in a user namespace as a normal user, so a break-out does not give host root.

Quick quiz

Q1. Why is mounting docker.sock risky?

Anyone who can use the socket can become root on the host.

Q2. What does DinD need that makes it risky?

--privileged.

Q3. Name two daemonless image builders.

Kaniko and Buildah.

Q4. What is rootless Docker?

A Docker daemon running as a normal user.

Q5. Is being in the docker group safe?

No. It is almost the same as root.

Samajla ka?

Ravi Sir's Cards for this lesson

मराठी cards