The Container Needs Docker, So Mount the Socket and Run --privileged?
Scenario
Your CI pipeline runs each job inside a throwaway container on a shared runner host. One stage needs to build a Docker image and push it to the registry — but inside the job container there's no Docker daemon, so docker build dies with a permission error and the pipeline is red. The detail that changes everything: this pipeline also runs on pull requests from external contributors. Code written by strangers on the internet executes in these job containers.
The Quick Fix on the Table
Mount the host's /var/run/docker.sock into the job container and add --privileged for good measure. It's a two-line runner config change, the build goes green immediately, and the team moves on.
The quick fix is on the table and the room is waiting for your call. Would you sign off on it? Take a position and justify it — out loud or on paper — before revealing the analysis.
Why the Quick Fix Fails
- The Docker socket is root on the host, full stop. Anyone who can talk to the daemon can run
docker run -v /:/host --privilegedand own the host filesystem, its credentials, and every other container on it. There is no permission model between "can use the socket" and "is root." - --privileged removes the walls that were left. It disables the seccomp profile, grants all capabilities, and exposes host devices. Combined with the socket mount it's not "elevated access," it's a formal handover of the machine.
- You are handing that root to untrusted code. External-contributor PRs run arbitrary code in these jobs. A malicious pull request doesn't need to find a container escape — you've configured one on purpose. From the runner host it can steal registry and cloud credentials, poison other teams' builds, or plant backdoored images.
- Jobs on the shared host stop being isolated from each other. Any job using the shared daemon can inspect, stop, or modify every other job's containers and read their environment — secrets included. One noisy or hostile job compromises all of them.
- It ossifies into permanent architecture. Once pipelines depend on the mounted socket, unwinding it means touching every job. "Temporary until we fix it properly" becomes the security finding your next audit opens with.
The interviewer nods: “Fine, the quick fix is off the table. So what exactly would you do — step by step?” Sketch your plan before revealing the approach.
The Right Approach
- Reframe the requirement. The job doesn't need "Docker" — it needs to build an OCI image and push it. Several tools do that without a daemon and without privilege, which dissolves the whole problem.
- Use a daemonless builder for the common case.
kanikobuilds a Dockerfile entirely in userspace inside an ordinary unprivileged container and pushes straight to the registry. Rootlessbuildkitdandbuildahare equivalent options depending on your stack. - If you must keep the Docker CLI workflow, isolate the daemon. Point
DOCKER_HOSTat a per-job rootless BuildKit/dind sidecar or a remote build service — never at the shared host daemon. The blast radius of a compromise becomes one disposable job environment. - Split trust tiers for external PRs. Untrusted PR jobs run tests only, on isolated ephemeral runners (e.g. one-shot VMs or Firecracker-style sandboxes), with no registry push credentials. Image build-and-push runs only after merge, on trusted runners, from reviewed code.
- Scope the credentials. Give build jobs a registry token that can push only to their own repository, short-lived where the registry supports it. A leaked token should be an annoyance, not a supply-chain event.
- Enforce it in config, not convention. Disable
--privilegedand host socket mounts in the runner configuration itself (most CI runners support denying these), so the shortcut can't quietly return in someone's job definition. - Add caching so nobody misses the daemon. Daemonless builds can use registry-backed layer caching (
kaniko --cache, BuildKit--cache-to/--cache-from). With warm caches, the secure path is usually as fast as the socket ever was.
Final pushback: “Your plan costs more time and money than the quick fix. Convince me.” How do you defend your position under pressure?
How to Defend It
- "Mounting docker.sock is giving the job root on the runner host — the daemon runs as root and executes whatever the socket asks. With --privileged on top, we're not bending the security model, we're deleting it."
- "These jobs run pull requests from people we've never met. This config turns 'open a PR' into 'get root on our CI infrastructure' — no exploit required, we built the door ourselves."
- "The job's actual requirement is build-and-push an image. Kaniko and rootless BuildKit do exactly that with no daemon, no privilege, and a one-day pipeline change."
- "On a shared daemon, every job can read every other job's containers and secrets. One compromised PR job means rotating credentials for every team on that runner."
- "I'd rather spend a day wiring kaniko than explain in an incident review why our registry images were backdoored through a drive-by pull request."