Skip to content
Community

EXPLORE TOPICS

Editorial approach

New to CoreTrailKit?

Observe your services. Keep the evidence close.

CoreTrailKit
Back to articles
GUIDE/ Docker

Docker volumes: fix non-root permission denied

The application starts after switching to a non-root user, but saving a result fails with Permission denied. A writable Docker mount does not mean every process inside the container can write every file on it.

By CoreTrailKit4 min read

01

Capture the operation that is actually denied

Read, append, create, and rename are different filesystem operations. An application may read a configuration file successfully but fail to create a new output file in the same directory. Capture the full path, operation, and error rather than reducing all storage failures to a Docker permission problem.

Check the mount mode as well. A read-only mount can reject writes even when the file owner is correct. Conversely, changing a mount to read-write cannot give an unrelated Linux UID access to a file with restrictive ownership.

02

Inspect identity and numeric ownership

Run diagnostics as the service user, not only from an interactive root shell. The numeric IDs matter even when a friendly username appears identical across two images. If the application drops privileges after startup, inspect the actual worker identity rather than assuming the image default tells the entire story.

The following commands assume a service called api and a Debian-like image with id and stat. Adapt the executable names for a minimal or distroless image; absence of a diagnostic shell is not evidence that the path is writable.

sh · EXAMPLE
docker compose exec api id
docker compose exec api stat -c '%u:%g %a %n' /data /data/result.txt
container_id=$(docker compose ps --all --quiet api)
docker inspect --format '{{json .Mounts}}' "$container_id"

03

Reproduce an append failure without touching project data

Create a new volume specifically for this example. The first container writes a file as root with mode 640. The second container attempts an append as UID/GID 10001:10001 and should fail. The volume name returned by Docker is used directly, so an existing application volume is not selected accidentally.

Our validation reproduced the denied append. This is stronger evidence than comparing usernames: it tests the operation under the intended numeric identity against the same stored file.

sh · EXAMPLE
demo_volume=$(docker volume create)
docker run --rm --mount "type=volume,src=$demo_volume,dst=/data"   python:3.12-slim sh -c   'printf "original\n" > /data/result.txt; chmod 640 /data/result.txt'
# Expected to fail: this UID does not own the file.
docker run --rm --user 10001:10001   --mount "type=volume,src=$demo_volume,dst=/data"   python:3.12-slim sh -c 'printf "new\n" >> /data/result.txt'

04

Repair the specific ownership relationship

For the disposable example, change ownership of just the result file. Keep mode 640. The non-root process should now append successfully while the original content remains. This does not require running the application permanently as root or setting every file to mode 777.

For an actual service, choose a stable runtime identity and arrange only its necessary writable paths. A Dockerfile ownership setting applies to image files; an already populated runtime volume has its own stored ownership. Do not recursively chown a shared database or a host project merely because one append failed.

sh · EXAMPLE
docker run --rm --mount "type=volume,src=$demo_volume,dst=/data"   python:3.12-slim chown 10001:10001 /data/result.txt
docker run --rm --user 10001:10001   --mount "type=volume,src=$demo_volume,dst=/data"   python:3.12-slim sh -c   'printf "new\n" >> /data/result.txt; stat -c "%u:%g %a" /data/result.txt; cat /data/result.txt'
# Remove only this example's new volume after reviewing the result.
docker volume rm "$demo_volume"

05

Use a separate branch when ownership already matches

If the numeric owner, parent directory permissions, and mount mode are appropriate but access still fails, inspect ACLs, security labels, user-namespace mappings, and the storage driver. A namespaced root user can map to an unprivileged host UID; copying host ownership assumptions into that environment can be wrong.

Close the investigation with the application operation itself: create or append the expected output as the service user and read it back. A successful root-shell touch command is not the acceptance test for a non-root service.

Know the limits

The reproduction uses an isolated Linux named volume, UID/GID 10001:10001, and one file. It does not validate host ACLs, SELinux labels, NFS root-squash, or rootless/user-namespace ID mappings. Docker Desktop bind mounts can behave differently from Linux-managed volumes. Reproduction checked on 26 October 2026 using Linux containers on Docker Engine 29.6.2 and Compose 5.3.1; this is the validation environment, not a minimum-version claim.

Sources & further reading

A technical publication by CoreTrailKit.

Browse all articles

CONTINUE EXPLORING