Ravindra BagaleCourses & study guides मराठी

Chapter 52: Dockerfile Instructions Explained: FROM, RUN, CMD vs ENTRYPOINT, COPY vs ADD, Multi-Stage and BuildKit

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.

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.

Socket mountagent uses host dockerd: fast, riskyDocker-in-Dockerown dockerd in a privileged sidecarKaniko or rootless BuildKitno daemon, no privilegeRegistryall three push here
Picture 1: three ways to build inside CI

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

Pod starts kaniko executorno dockerd, not privilegedReads context and Dockerfilefrom a volume or GitRuns each step in user spacewrites layersPushes to registrywith cache repo
Picture 2: Kaniko on Kubernetes

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.

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.

Lab: try at home

  1. Build with a socket mount and list host containers.
  2. Start a DinD daemon with TLS and build.
  3. Write a GitLab CI job with a dind service.
  4. Run Kaniko locally with --no-push.
  5. Run Kaniko as a Kubernetes pod.
  6. 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.

Ravi Sir's Cards for this lesson

मराठी cards