Docker audit fix

How to Check and Configure Default Docker Image Users

An image that declares no USER runs as root by default, so any process that escapes the application owns uid 0 inside the container. Add a numeric non-root USER at the end of the Dockerfile, after the installs and the chowns.

Hiren KalariyaLast reviewed: Oct 12, 2026Check image-configuration-review
Medium
severity
Yes
needs root or sudo
3
results it can return
4 of 8
checks in this audit

Every threshold on this page is transcribed from the docker-security-image-vulnerabilities audit script that CtrlOps runs, and a build check fails if the two ever disagree. See all 8 Image Vulnerabilities checks, or how the audit runs.

Medium severityNeeds sudo

What this check reads

The check takes the first 10 tagged local images from docker images --format '{{.Repository}}:{{.Tag}}', <none> entries excluded, and reads docker inspect -f '{{.Config.User}}' <img> for each of them. Three values count as root: an empty string, the literal root, and 0. If none of the sampled images fall into that group the check PASSes; if any do it WARNs and names at most 5 of them, and an empty image list is the SKIP.

When it applies

Needs the docker binary and daemon access; otherwise SKIP. It inspects the first 10 tagged local images and reads .Config.User from each - the USER declared in the image, with empty, root and 0 all counting as root. This is about images, not running containers: an image with no USER that is always run with -u 1000 still produces a finding, and conversely an image with a USER can be overridden at run time. The running-container equivalent is the Container User check. Despite the check's name, only the default user is reviewed - exposed ports, healthchecks and other image configuration are not examined here.

What each result means

Result thresholds this check applies
ResultWhenWhat it means
SKIPDocker not installed, daemon unreachable, or no local imagesNothing was inspected. No image list was produced, so the check says nothing about the default user of anything on this host.
PASSEvery image sets a non-root USEREvery image in the sample declares a USER that is neither root nor 0. A run-time --user override can still change what the container actually runs as.
WARNSome images have no USERAt least one sampled image would run as root unless the run command overrides it, and the message names up to five.

Why it matters

Same principle as Container User but at the image level: CIS Docker 4.1 "Ensure that a user for the container has been created"; Dockerfile USER; Dockle CIS-DI-0001. Images without USER run as root unless overridden at run time.

Why it fails, and when it is wrong

  • Official images (nginx, postgres, redis, mysql, mongo, node, python) ship without USER on purpose (entrypoint drops privileges, or they leave it to the operator). This WARN is informational for those; the Container User check on the running container is the one that matters.
  • Images pulled only as build stages or caches are included in the sample.
  • Sample is 10 images, in docker images order (newest first).
  • Only the exact values root and 0 count, alongside an empty one. USER 0:0 and USER root:root name a group as well, match neither string, and pass as non-root. The Container User check does treat those forms as root on the running container.

How to fix it

Create the account and hand it its files

Do this after the package installs, which still need root.

RUN groupadd -r -g 10001 app && useradd -r -u 10001 -g app app \
 && chown -R 10001:10001 /app

That is the Debian, Ubuntu and RHEL form. On Alpine use addgroup -S -g 10001 app && adduser -S -u 10001 -G app app.

Switch with a numeric USER

USER 10001:10001

Put it after every instruction that needs root and before CMD. A numeric ID does not depend on an /etc/passwd lookup at run time.

Rebuild and confirm

docker build -t app:1.4.3 .
docker run --rm app:1.4.3 id
# expected: uid=10001, not uid=0(root)

For images you do not build

You cannot change someone else's image. Use its non-root variant where one exists, derive your own image that adds a USER line, or set the user at run time with --user or Compose user:, and record the base-image finding as accepted.

Verify the fix

docker images --format '{{.Repository}}:{{.Tag}}' | grep -v '<none>' | \
  xargs -I{} sh -c 'printf "%s user=%s\n" {} "$(docker image inspect -f "{{.Config.User}}" {})"'
# expected: a non-root user or UID for every image

# Confirm a rebuilt image really runs as that user:
docker run --rm <image> id
# expected: uid=10001 (or whatever you set), not uid=0(root)

Debugging

Three different cases share this status. 'Docker not installed' is correct on a host with no Docker. 'Cannot query Docker daemon' means the audit account could not reach the daemon: check systemctl is-active docker, then docker ps as that user and sudo docker ps, and run the audit as root or with sudo if only the second works. 'No local images found' means the daemon held no tagged images, which is normal on a host that pulls at deploy time; check with docker images -a. It is also what you get when the audit has root but the daemon is down, because an empty answer and an empty image list look the same to the script. The Docker Inventory check covers the unreachable case in more detail.

Most official images (nginx, postgres, node) ship without a USER by design and expect you to set it at run time, or drop privileges in their entrypoint. You cannot fix someone else's image; set the user when you run it (--user, Compose user:) or in your own derived image, and record the base-image finding as accepted.

The image's USER can be overridden at run time. Cross-check with the Container User check and docker inspect -f '{{.Config.User}}' $(docker ps -q).

The instruction applies to everything after it, so package installs and chowns must come first. Create the account explicitly and give it ownership: RUN useradd -u 10001 -r app && chown -R 10001 /app, then USER 10001. Prefer a numeric UID so the runtime does not depend on /etc/passwd lookups.

The sample is capped at 10 images and the message lists at most 5. Use the command above for the complete picture.

Sources

How the script reads this

Next

Re-run the Image Vulnerabilities audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 8 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.

All 8 Image Vulnerabilities fixes
Audit your fleet

Find every one of these on every server, in one click

CtrlOps runs this audit over your existing SSH connection - no agents, no scripts to manage. $7/user/month after a 1 month free trial - no credit card required.

Windows

✓ Start instantly·✓ No credit card·✓ No sneaky autorenewals