A docker.sock mounted in a container means the host’s Docker API socket, /var/run/docker.sock, is shared into a pod or container, which gives that container root-equivalent control of the host. To fix it, delete the hostPath volume or -v bind mount, move image builds to a rootless or daemonless builder, and block the mount with admission policy.
What the scanner is actually detecting
Both scanners below are configuration checks: they report how a workload is set up, not evidence of abuse.
| Scanner | ID and title | What it matches |
|---|---|---|
| Trivy (Kubernetes misconfiguration) | KSV-0006 (aliases KSV006, AVD-KSV-0006, no-docker-sock-mount): hostPath volume mounted with docker.sock, severity HIGH | A volume whose hostPath.path is exactly /var/run/docker.sock in a Pod, Deployment, StatefulSet, DaemonSet, ReplicaSet, ReplicationController, Job, CronJob or OpenShift DeploymentConfig |
| docker-bench-security | 5.32: Ensure that the Docker socket is not mounted inside any containers (Automated) | Runs docker inspect –format ‘{{ .Mounts }}’ on each running container and warns if the output contains docker.sock |
Trivy raises KSV-0006 when scanning manifests and Helm charts (trivy config) or a live cluster (trivy k8s). docker-bench-security follows the CIS Docker Benchmark v1.6.0 and only sees containers that are running when it executes.
Real-world risk
Docker’s security documentation states that only trusted users should be allowed to control the Docker daemon, because the API can start a container with the host’s root directory mounted and alter the host filesystem without restriction. The post-install guide warns that the docker group grants root-level privileges for the same reason. Any process that can talk to the socket has that power.
The mount turns a compromise of that container into a compromise of the host. Typical entry points are a CI job running untrusted pull request code, a malicious build dependency, or a vulnerable web UI in a management or monitoring tool. On a Kubernetes node, host root also exposes the kubelet’s credentials and every other pod scheduled there.
Two honest caveats. The finding is not remotely exploitable on its own; it widens the blast radius of some other flaw. And since Kubernetes v1.24 removed dockershim, many nodes run only containerd or CRI-O, so the mount may point at nothing. Remove it anyway; it becomes live if Docker Engine is ever installed there. Mounting it read-only does not help: the Kubernetes volumes documentation warns that hostPath mounts, read-only or read-write, can expose privileged APIs such as the container runtime socket for container escape.
How to confirm it on the host
On Kubernetes, list pods that mount a docker.sock path from the node, then find the controller that owns each one:
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"t"}{.spec.volumes[*].hostPath.path}{"n"}{end}' | grep docker.sock
kubectl get pod <pod> -n <namespace> -o jsonpath='{.metadata.ownerReferences[0].kind}/{.metadata.ownerReferences[0].name}{"n"}'
Then find the source that deploys it, since fixing only the live object is undone by the next Helm release or GitOps sync:
grep -rn "docker.sock" ./manifests ./charts
trivy config --helm-values values-prod.yaml ./charts/my-app
trivy k8s --scanners=misconfig --report=summary
On a standalone Docker host, check every container, including stopped ones that could be restarted:
docker ps -aq | xargs -r docker inspect --format '{{.Name}} {{range .Mounts}}{{.Source}}:{{.Destination}} {{end}}' | grep docker.sock
docker compose config | grep -n docker.sock
sudo sh docker-bench-security.sh -c check_5_32
How to fix it
Remove the mount from Kubernetes workloads
Delete both the volume and its volumeMount in the manifest, chart template or values file, then redeploy through your normal pipeline:
containers:
- name: app
volumeMounts:
- name: docker-sock # delete this entry
mountPath: /var/run/docker.sock
volumes:
- name: docker-sock # delete this entry
hostPath:
path: /var/run/docker.sock
Do not replace it with /run/containerd/containerd.sock or /var/run/crio/crio.sock. Those are the default CRI sockets for containerd and CRI-O and carry the same host-level power.
CI image builds: use a rootless or daemonless builder
Most socket mounts exist so a CI runner can call docker build and docker push. BuildKit publishes a rootless image and Kubernetes examples for this case. A condensed container spec based on the upstream examples/kubernetes/job.rootless.yaml:
containers:
- name: build
image: moby/buildkit:rootless
env:
- name: BUILDKITD_FLAGS
value: --oci-worker-no-process-sandbox
command: ["buildctl-daemonless.sh"]
args: ["build", "--frontend", "dockerfile.v0",
"--local", "context=/workspace", "--local", "dockerfile=/workspace",
"--output", "type=image,name=registry.example.com/team/app:1.0,push=true"]
securityContext:
runAsUser: 1000
runAsGroup: 1000
seccompProfile:
type: Unconfined
appArmorProfile: # Kubernetes 1.30 and later
type: Unconfined
To push, mount a registry config.json from a Secret and set DOCKER_CONFIG to its directory. The trade-off: unconfined seccomp and AppArmor profiles are not allowed under the Pod Security baseline level, so run builders in a dedicated namespace with its own policy. A non-root builder with relaxed syscall filtering is still a far smaller grant than root on the node.
Buildah is another daemonless option; its quay.io/buildah/stable image defaults to BUILDAH_ISOLATION=chroot. Kaniko was the traditional answer, but the GoogleContainerTools repository is archived and no longer maintained, so avoid it for new pipelines. Pair the change with scanning Docker images in CI so the new builder also gates what gets pushed.
Monitoring agents on Kubernetes
Per-container metrics are available from the kubelet (/metrics/cadvisor and /stats/summary) without any runtime socket. Grant only the matching subresources:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-metrics-reader
rules:
- apiGroups: [""]
resources: ["nodes/metrics", "nodes/stats"]
verbs: ["get"]
Avoid nodes/proxy: the kubelet authorization documentation warns that even get on it authorizes executing commands in any container on the node.
Standalone Docker and Compose hosts
Remove -v /var/run/docker.sock:/var/run/docker.sock from the docker run command, or the same entry from the service’s volumes: list in the compose file, then recreate the container with docker compose up -d –force-recreate <service>. If a tool genuinely needs the Docker API, such as a reverse proxy that discovers containers by label, prefer its file-based configuration. Where that is impossible, a rootless daemon (socket at, for example, unix:///run/user/1000/docker.sock) limits the damage to one unprivileged user’s containers.
Block the mount with admission policy
The Pod Security baseline level forbids all hostPath volumes. Preview the impact, then enforce namespace by namespace:
kubectl label --dry-run=server --overwrite ns --all pod-security.kubernetes.io/enforce=baseline
kubectl label --overwrite ns ci pod-security.kubernetes.io/enforce=baseline
Where a namespace legitimately needs other hostPath mounts (a log collector reading /var/log, for example), a ValidatingAdmissionPolicy (stable since Kubernetes v1.30) can target runtime sockets only:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: deny-runtime-socket-mounts
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: >-
!has(object.spec.volumes) || object.spec.volumes.all(v, !has(v.hostPath) ||
!(v.hostPath.path.endsWith('docker.sock') || v.hostPath.path.endsWith('containerd.sock') ||
v.hostPath.path.endsWith('crio.sock')))
message: "hostPath mounts of container runtime sockets are not allowed"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: deny-runtime-socket-mounts
spec:
policyName: deny-runtime-socket-mounts
validationActions: [Warn, Audit] # change to [Deny] once warnings stop
This policy does not catch a mount of the parent directory (/var/run or /run); the baseline level does. Because it matches pods, a bad Deployment is still accepted but its ReplicaSet cannot create pods, so watch ReplicaSet events after switching to Deny. Admission control is only one layer of workload protection; the CSPM vs CWPP vs CNAPP comparison covers where runtime tooling fits.
How to verify the fix and rescan
- Rerun the kubectl jsonpath and docker inspect commands above. Both should print nothing.
- Apply a throwaway manifest that mounts the socket with kubectl apply –dry-run=server -f sock-test.yaml. You should see a warning (Warn) or a rejection (Deny).
- Confirm the pipeline still pushes images and dashboards still show per-container metrics.
- Rescan: trivy config and trivy k8s –scanners=misconfig –report=summary should no longer list KSV-0006, and docker-bench-security.sh -c check_5_32 should report PASS.
Watch for a scanner-only fix. KSV-0006 matches the literal path /var/run/docker.sock, so a workload changed to mount /run/docker.sock or the whole /var/run directory passes Trivy while exposing the same socket.
What can break and how to roll back
- CI jobs that still call the Docker CLI fail with “Cannot connect to the Docker daemon at unix:///var/run/docker.sock”. Integration tests that start containers need a dedicated build VM or another approach.
- Build caching changes, since builds no longer share the node’s layer cache. BuildKit’s –export-cache and –import-cache options can use a registry cache instead.
- Reverse proxies and management UIs lose automatic container discovery.
- Admission policy can block a system DaemonSet on its next rollout, which is why the binding starts in Warn and Audit mode.
To roll back a workload, revert the manifest commit and redeploy, or run kubectl rollout undo deployment/<name> -n <namespace> as a stopgap. Remove a Pod Security label with kubectl label ns <namespace> pod-security.kubernetes.io/enforce-, and relax the policy by setting validationActions back to [Warn, Audit]. On Docker hosts, restore the compose file and run docker compose up -d. Record any rollback as a temporary exception.
Common false positive reasons
- Rootless Docker socket. docker-bench matches any mount containing the text docker.sock, so /run/user/1000/docker.sock from a rootless daemon is flagged. The risk is lower, but still review it.
- Chart defaults, not deployed values. trivy config evaluates a Helm chart with its default values unless you pass –helm-values. If production overrides disable the mount, scan with the real values file.
- Stale results. A scan taken before the rollout, or of a container since removed.
- No Docker on the node. On a containerd-only node the mount may point at nothing. That is a real misconfiguration, just a lower priority one.
FAQ
Does mounting docker.sock read-only fix the finding?
No. A read-only flag stops file writes, not API calls over the socket. Kubernetes warns about runtime socket exposure through read-only hostPath mounts too, and neither scanner treats read-only as a pass.
Is Docker-in-Docker a safer alternative?
Not really. The Docker-in-Docker image needs a privileged container, which the Pod Security baseline level forbids because privileged pods disable most security mechanisms. A rootless builder is the better replacement.
Are containerd.sock and crio.sock any safer?
No. They are the runtime’s own control sockets, and the Kubernetes volumes documentation names the container runtime socket as a privileged API usable for container escape.
Can I keep the mount for one trusted tool?
Only as a documented exception: restrict who can deploy to that namespace, pin the workload to dedicated nodes, and exempt it by name in your admission policy.
Tracking this finding across many hosts
docker.sock mounts return whenever a chart or compose file is copied from an old template. If you consolidate scanner output, SITEY is one option: it imports findings from 16 scanners, merges duplicates per scanner (not across scanners), and uses AI triage to suggest false positives with evidence while a human decides. Host-specific fix scripts go through human approval before SITEY agents apply them on Linux hosts, and you can confirm closure by rerunning the scanner that raised the finding.