52.12 Building images in CI: DinD vs socket mount vs Kaniko
Friends, on your laptop docker build just works. But in Jenkins or GitLab, the build itself often runs inside a container. How does a container build another container? There are three common answers: Docker-in-Docker, mounting the host Docker socket, and daemonless builders like Kaniko or BuildKit rootless. Today we try all three and learn when to pick which.
मित्रांनो, laptop वर docker build सहज चालतं. पण Jenkins किंवा GitLab मध्ये build स्वतःच एका container मध्ये चालतो. मग container दुसरा container कसा build करणार? तीन उत्तरं आहेत: Docker-in-Docker, host चा Docker socket mount करणं, आणि Kaniko किंवा rootless BuildKit सारखे daemon नसलेले builders. आज तिन्ही करून बघू आणि कधी कोणतं वापरायचं ते शिकू.
दोस्तों, laptop पर docker build आराम से चलता है. पर Jenkins या GitLab में build खुद एक container में चलता है. तो container दूसरा container कैसे build करेगा? तीन जवाब हैं: Docker-in-Docker, host का Docker socket mount करना, और Kaniko या rootless BuildKit जैसे बिना daemon वाले builders. आज तीनों करके देखेंगे और सीखेंगे कब कौन सा चुनें.
1. Start from a story
Sneha ran the Jenkins platform at Myntra. To make image builds work quickly, someone had mounted /var/run/docker.sock into every build agent.
One day a test job ran docker rm -f on a filter that matched too much. It deleted containers of other teams running on the same host, including a staging database.
Sneha moved shared agents on Kubernetes to Kaniko and rootless BuildKit, which need no Docker daemon and no privileged access. A few special jobs that need docker run for integration tests got isolated Docker-in-Docker sidecars.
Builds became a little slower at first, then faster after she added registry cache. No job could touch another team's containers again. Today we compare the three methods by running them.
2. Method 1: mount the Docker socket
Step 1: What it is
Why: The CI container talks to the host Docker daemon.
What: Mount /var/run/docker.sock. The docker CLI inside sends commands to the host.
Example 1: Try it
Type this:
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock docker:27-cli docker ps --format '{{.Names}}' | head -3
You will see:
jenkins
redis-cache
postgres-staging
What it means: It sees every container on the host.
Example 2: Build works
Type this:
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v $PWD:/src -w /src docker:27-cli docker build -q -t shop:ci .
You will see:
sha256:7b3c...
What it means: Fast, reuses host cache.
Example 3: Image lands on host
Type this:
docker images shop:ci --format '{{.Repository}}:{{.Tag}}'
You will see:
shop:ci
What it means: The build ran on the host daemon.
Step 2: Why it is risky
Why: The socket is root on the host.
What: Anyone with the socket can start a privileged container that mounts the host disk.
Example 4: Host files
Type this:
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock docker:27-cli docker run --rm -v /:/host alpine:3.20 ls /host/etc | head -2
You will see:
adduser.conf
alternatives
What it means: A job can read the whole host.
Example 5: Other teams
Type this:
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock docker:27-cli docker ps -q | wc -l
You will see:
14
What it means: It can stop all 14 containers.
Example 6: Rule
Type this:
printf 'Use socket mounts only on single-tenant, trusted agents.\n'
You will see:
Use socket mounts only on single-tenant, trusted agents.
What it means: Never on shared runners.
Step 3: Jenkins agent with the socket
Why: Common in small setups.
What: A Jenkinsfile docker agent with args.
Example 7: Jenkinsfile
Type this:
printf 'pipeline {\n agent { docker { image "docker:27-cli"\n args "-v /var/run/docker.sock:/var/run/docker.sock" } }\n stages { stage("b") { steps { sh "docker build -t shop:${BUILD_NUMBER} ." } } }\n}\n' > Jenkinsfile && grep args Jenkinsfile
You will see:
args "-v /var/run/docker.sock:/var/run/docker.sock" } }
What it means: Socket passed in.
Example 8: Run
Type this:
(Jenkins console)
You will see:
+ docker build -t shop:42 .
=> => naming to docker.io/library/shop:42
What it means: Build number tag.
Example 9: Group not mode
Type this:
ls -l /var/run/docker.sock
You will see:
srw-rw---- 1 root docker 0 Oct 25 09:00 /var/run/docker.sock
What it means: Access is by the docker group. Never loosen the mode.
3. Method 2: Docker-in-Docker
Step 4: What DinD is
Why: A full dockerd inside a container.
What: The docker:dind image runs its own daemon. It must be privileged.
Example 10: Start
Type this:
docker network create ci && docker run -d --privileged --name dind --network ci -e DOCKER_TLS_CERTDIR=/certs -v dind-certs:/certs docker:27-dind
You will see:
a3c9e1...
What it means: A separate daemon.
Example 11: Client
Type this:
docker run --rm --network ci -e DOCKER_HOST=tcp://dind:2376 -e DOCKER_TLS_VERIFY=1 -e DOCKER_CERT_PATH=/certs/client -v dind-certs:/certs:ro docker:27-cli docker ps
You will see:
CONTAINER ID IMAGE COMMAND
What it means: Empty: it cannot see host containers.
Example 12: Build
Type this:
docker run --rm --network ci -e DOCKER_HOST=tcp://dind:2376 -e DOCKER_TLS_VERIFY=1 -e DOCKER_CERT_PATH=/certs/client -v dind-certs:/certs:ro -v $PWD:/src -w /src docker:27-cli docker build -q -t shop:dind .
You will see:
sha256:51ae...
What it means: Built inside the inner daemon.
Step 5: GitLab CI with DinD
Why: The classic setup.
What: Use a dind service and point DOCKER_HOST at it.
Example 13: File
Type this:
printf 'build:\n image: docker:27-cli\n services: ["docker:27-dind"]\n variables:\n DOCKER_TLS_CERTDIR: "/certs"\n script:\n - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .\n - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA\n' > .gitlab-ci.yml && grep services .gitlab-ci.yml
You will see:
services: ["docker:27-dind"]
What it means: DinD as a service.
Example 14: Job log
Type this:
(GitLab job log)
You will see:
$ docker build -t registry.gitlab.example.com/shop/api:4e7b1c2 .
=> => naming to ...
What it means: Tag is the commit.
Example 15: Runner config
Type this:
grep privileged /etc/gitlab-runner/config.toml
You will see:
privileged = true
What it means: The runner must allow privileged.
Step 6: DinD trade-offs
Why: Isolation, but privileged and cold cache.
What: Each job starts with an empty cache unless you push and pull cache.
Example 16: Cold
Type this:
time docker build -t shop:dind . 2>&1 | tail -1
You will see:
real 2m41s
What it means: Every layer built again.
Example 17: Registry cache
Type this:
docker buildx build --cache-from type=registry,ref=registry.example.com/shop:cache --cache-to type=registry,ref=registry.example.com/shop:cache,mode=max -t registry.example.com/shop:1.0 --push .
You will see:
=> CACHED [4/6] RUN npm ci
What it means: Warm build again.
Example 18: Clean up
Type this:
docker rm -f dind && docker volume rm dind-certs && docker network rm ci
You will see:
dind
dind-certs
ci
What it means: Nothing left on the host.
4. Method 3: daemonless builders
Step 7: Kaniko basics
Why: Build from a normal container.
What: gcr.io/kaniko-project/executor reads a Dockerfile and pushes the result. It never needs a daemon.
Example 19: Run locally
Type this:
docker run --rm -v $PWD:/workspace gcr.io/kaniko-project/executor:v1.23.2 --context=/workspace --dockerfile=Dockerfile --no-push --destination=shop:kaniko
You will see:
INFO Built cross stage deps
INFO Skipping push to container registry due to --no-push flag
What it means: Built without dockerd.
Example 20: Tar output
Type this:
docker run --rm -v $PWD:/workspace gcr.io/kaniko-project/executor:v1.23.2 --context=/workspace --no-push --destination=shop:k --tar-path=/workspace/shop.tar && ls shop.tar
You will see:
shop.tar
What it means: Load it with docker load if needed.
Example 21: Registry login file
Type this:
printf '{"auths":{"registry.example.com":{"auth":"ZXhhbXBsZTpub3QtcmVhbA=="}}}' > config.json && cat config.json | head -c 30
You will see:
{"auths":{"registry.example.c
What it means: Example credentials only; mount at /kaniko/.docker.
Step 8: Kaniko in a Kubernetes pod
Why: The common enterprise setup.
What: Run it as a pod or a Jenkins Kubernetes agent container.
Example 22: Pod YAML
Type this:
printf 'apiVersion: v1\nkind: Pod\nmetadata: {name: kaniko}\nspec:\n restartPolicy: Never\n containers:\n - name: kaniko\n image: gcr.io/kaniko-project/executor:v1.23.2\n args: ["--context=git://git.example.com/team/shop.git#refs/heads/main","--destination=registry.example.com/shop:1.0","--cache=true"]\n volumeMounts: [{name: regcfg, mountPath: /kaniko/.docker}]\n volumes: [{name: regcfg, secret: {secretName: regcfg}}]\n' > kaniko.yaml && kubectl apply -f kaniko.yaml
You will see:
pod/kaniko created
What it means: Context pulled from Git.
Example 23: Logs
Type this:
kubectl logs -f kaniko | tail -2
You will see:
INFO Pushing image to registry.example.com/shop:1.0
INFO Pushed registry.example.com/shop@sha256:c1d2...
What it means: Pushed with no privilege.
Example 24: Privileged?
Type this:
kubectl get pod kaniko -o jsonpath='{.spec.containers[0].securityContext.privileged}'; echo " (empty)"
You will see:
(empty)
What it means: Not privileged.
Step 9: Rootless BuildKit
Why: Full BuildKit features without root.
What: moby/buildkit:rootless runs buildkitd as a normal user. buildctl or buildx connects to it.
Example 25: Start
Type this:
docker run -d --name bk --security-opt seccomp=unconfined --security-opt apparmor=unconfined moby/buildkit:v0.16.0-rootless --oci-worker-no-process-sandbox
You will see:
e81f...
What it means: Daemon as user 1000.
Example 26: Builder
Type this:
docker buildx create --name rootless --driver remote docker-container://bk --use
You will see:
rootless
What it means: buildx uses it.
Example 27: Build
Type this:
docker buildx build -t registry.example.com/shop:1.1 --push . 2>&1 | tail -1
You will see:
=> pushing manifest for registry.example.com/shop:1.1
What it means: Secrets, cache mounts and multi-arch still work.
Step 10: Kaniko cache and speed
Why: Daemonless does not mean slow.
What: --cache=true with a cache repo reuses layers across runs.
Example 28: First
Type this:
time kubectl logs kaniko | grep -c 'Taking snapshot'
You will see:
6
real 3m02s
What it means: Cold build.
Example 29: Second
Type this:
kubectl delete pod kaniko && kubectl apply -f kaniko.yaml && sleep 60 && kubectl logs kaniko | grep -c 'Found cached layer'
You will see:
4
What it means: Four layers from cache.
Example 30: Note
Type this:
printf 'Kaniko is in maintenance mode; many teams now prefer rootless BuildKit.\n'
You will see:
Kaniko is in maintenance mode; many teams now prefer rootless BuildKit.
What it means: Check the project status before choosing.
Step 11: Choose the method
Why: Pick by trust and need.
What: Single trusted host: socket. Need docker run in tests: DinD. Shared Kubernetes runners: Kaniko or rootless BuildKit.
Example 31: Trusted single agent
Type this:
printf 'socket mount\n'
You will see:
socket mount
What it means: Fast and simple.
Example 32: Integration tests with containers
Type this:
printf 'DinD sidecar\n'
You will see:
DinD sidecar
What it means: Isolated daemon per job.
Example 33: Shared Kubernetes runners
Type this:
printf 'Kaniko or rootless BuildKit\n'
You will see:
Kaniko or rootless BuildKit
What it means: No privilege needed.
Step 12: Log in to the registry safely
Why: Every method must push, so every method must log in.
What: Read the password from stdin or a credential store, never from a command-line argument.
Example 34: stdin login
Type this:
printf '%s' "$REG_PASS" | docker login registry.example.com -u ci-bot --password-stdin
You will see:
Login Succeeded
What it means: The password never shows in ps or history.
Example 35: ECR
Type this:
aws ecr get-login-password --region ap-south-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.ap-south-1.amazonaws.com
You will see:
Login Succeeded
What it means: Short-lived token from the IAM role.
Example 36: Log out
Type this:
docker logout registry.example.com
You will see:
Removing login credentials for registry.example.com
What it means: Clean the agent after the job.
5. Common mistakes and how Sneha avoids them
Mistake one: socket mounts on shared runners. Sneha uses them only on single-tenant agents.
Mistake two: making the socket world-writable to fix permission errors. Sneha adds the agent user to the docker group instead.
Mistake three: DinD with no TLS. Sneha keeps DOCKER_TLS_CERTDIR set.
Mistake four: no cache in DinD or Kaniko. Sneha uses a registry cache repo.
Mistake five: real registry passwords in YAML. Sneha keeps them in Jenkins credentials or Kubernetes secrets, and uses example values in docs.
Check yourself
Q1. Why is a socket mount risky?
It gives root-level control of the host daemon.
Q2. What does DinD need?
A privileged container running its own dockerd.
Q3. What makes Kaniko different?
It needs no daemon and no privilege.
Q4. What to use on shared Kubernetes runners?
Kaniko or rootless BuildKit.
Friends, speed, isolation and safety; each method trades them differently. Did you understand? Let us try it once more. Build the same app with a socket mount, DinD and Kaniko, and note the time and privileges each needed.
मित्रांनो, speed, isolation आणि safety; प्रत्येक पद्धत यांचा वेगळा तोल साधते. समजलं का? चला, पुन्हा एकदा करून पाहू. एकच app socket mount, DinD आणि Kaniko ने build करा, आणि प्रत्येकाला किती वेळ आणि कोणते privileges लागले ते लिहा.
दोस्तों, speed, isolation और safety; हर तरीका इनका अलग संतुलन रखता है. समझ आया? चलिए, एक बार फिर करके देखते हैं। एक ही app socket mount, DinD और Kaniko से build कीजिए, और हर एक में कितना समय और कौन से privileges लगे यह लिखिए.
Ravindra Bagale's Tip
In interviews, if asked how you build images in Kubernetes-based CI, answer: Kaniko or rootless BuildKit, with registry cache, and never the host socket on shared nodes.
Interview मध्ये Kubernetes वरच्या CI मध्ये image कशी build करता असं विचारलं तर सांगा: Kaniko किंवा rootless BuildKit, registry cache सोबत, आणि shared nodes वर host socket कधीच नाही.
Interview में पूछा जाए कि Kubernetes वाले CI में image कैसे build करते हैं, तो कहिए: Kaniko या rootless BuildKit, registry cache के साथ, और shared nodes पर host socket कभी नहीं.
Lab: try at home
- Build with a socket mount and list host containers.
- Start a DinD daemon with TLS and build.
- Write a GitLab CI job with a dind service.
- Run Kaniko locally with --no-push.
- Run Kaniko as a Kubernetes pod.
- Create a rootless BuildKit builder and push.
Recap
- Socket mount is fast but gives host control.
- DinD isolates but needs privileged.
- Kaniko builds without a daemon.
- Rootless BuildKit keeps every BuildKit feature.
- Use registry cache everywhere.
- Choose by trust and need.
Revision card
Interview card