[Help Needed] How to mount a directory as a non-root user in a container

VM hypervisor: libvirtd (libvirt) 8.0.0
Guest VM OS: Ubuntu 22.04.4 LTS x86_64
Docker Container image base (inside VM): FROM node:22-alpine
Docker version 26.1.3, build b72abbb

I have an application inside a docker container which needs to make writes to the internal mount path “/data/”. I have set up a new non-root user for this in my Dockerfile and set this to run with USER command in the Dockerfile. If I run the resulting container with /bin/sh in interactive mode, I can navigate to the “/data/” mount point and perform a “ls -l” on that:

/data $ ls -lah
total 338M
drwxr-xr-x 1 root root 8.0K May 28 17:59 .
drwxr-xr-x 1 root root 4.0K May 28 20:22 ..
-rwxrw-rw- 1 root root 5.0K Apr 29 20:48 .bashrc
drwxr-xr-x 1 root root    0 May 15 16:44 Browser

Then I see that user and group are both “root” and when I “touch /data/test.txt” (as the non-root user), I get a permission denied error. This all works fine if I run the container as “root” user.

/app $ whoami
/app $ id
uid=100(app) gid=1001(app) groups=1001(app)
/app $ touch /data/test.txt
touch: cannot touch '/data/test.txt': Permission denied

But best practice tells us to specify a non-root user and run with least privilege. But the mount is owned by “root” within the container. How do we specify that the owner of the mounted directory and all included files are owned by our “non-root user” within the container? I have tried to “docker run --user 1000:1000” to run the container, but all this does is run the container with the non-root user and the “/data/” mount is still using “root”.

Please advise how to run a container with a mount, where the internal mount point is owned completely by a specified non-root user?

The permission system is the same in Linux and containers. If a file or folder is owned by root, and there are no group or world write (“w”) permissions on it, then you can’t write as different user.

If you bind a host folder into a container folder, it is mounted into the container folder by inode. If the source folder on the host is owned by root, it won’t change just because you bind it into a container.

If you want to write into the folder, you need to make sure the uid and optionally the gid that own the host folder aligns with the uid:gid user inside the container.

You need to chown the folder on the host. Though, the output of ls -lah looks like this is the fresh home directory of the root user. Applying chown here will break things.

I created a new share on the ubuntu docker host. Details:

user@ubuntu-vm:~/testshare$ id
uid=1000(user) gid=1000(user) groups=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),122(lpadmin),135(lxd),136(sambashare),999(docker)
user@ubuntu-vm:~/testshare$ ls -lah
total 12K
drwxrwxr-x  3 user user 4.0K May 29 09:44 .
drwxr-x--- 24 user user 4.0K May 29 09:39 ..
drwxrwxr-x  2 user user 4.0K May 29 09:44 DirCreatedByHostUser
-rw-rw-r--  1 user user    0 May 29 09:44 FileCreatedByHostUser.txt

I reused the existing “node” user (which has same uid=gid=1000) within the container and show details of “testshare” as it appears within the container as “/data”.

/data $ id
uid=1000(node) gid=1000(node) groups=1000(node)
/data $ ls -lah
total 12K
drwxrwxr-x 3 root root 4.0K May 29 08:44 .
drwxr-xr-x 1 root root 4.0K May 29 08:42 ..
drwxrwxr-x 2 root root 4.0K May 29 08:44 DirCreatedByHostUser
-rw-rw-r-- 1 root root    0 May 29 08:44 FileCreatedByHostUser.txt
/data $ touch test.txt
touch: cannot touch 'test.txt': Permission denied

On the ubuntu host, here is my command to launch an interactive shell within the container:

docker run --rm -it --env-file env.list -v /home/user/testshare:/data --user 1000:1000 -p 3000:3000 mycontainer /bin/sh

Is there something else I am missing? I have ensured that the external directory permissions are all 1000:1000, which then match against the internal “node” container user again with 1000:1000. Yet I still face the same issue.

Is it Docker Desktop or Docker CE? If it is not Docker Desktop, you must have enabled Rootless Docker. The output of the following commands can help to figure it out:

docker version
docker context ls

If none of the above happened, maybe you just enabled a user namespace.

I have installed Docker Desktop myself.
Here is the output of these commands…

user@ubuntu-vm:~/testshare$ docker version
Client: Docker Engine - Community
 Cloud integration: v1.0.35+desktop.13
 Version:           26.1.3
 API version:       1.45
 Go version:        go1.21.10
 Git commit:        b72abbb
 Built:             Thu May 16 08:33:29 2024
 OS/Arch:           linux/amd64
 Context:           desktop-linux

Server: Docker Desktop 4.30.0 (149282)
  Version:          26.1.1
  API version:      1.45 (minimum version 1.24)
  Go version:       go1.21.9
  Git commit:       ac2de55
  Built:            Tue Apr 30 11:48:28 2024
  OS/Arch:          linux/amd64
  Experimental:     false
  Version:          1.6.31
  GitCommit:        e377cd56a71523140ca6ae87e30244719194a521
  Version:          1.1.12
  GitCommit:        v1.1.12-0-g51d5e94
  Version:          0.19.0
  GitCommit:        de40ad0
user@ubuntu-vm:~/testshare$ docker context ls
NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT                                 KUBERNETES ENDPOINT   ORCHESTRATOR
default             moby                Current DOCKER_HOST based configuration   unix:///var/run/docker.sock                                           
desktop-linux *     moby                Docker Desktop                            unix:///home/user/.docker/desktop/docker.sock                         

That is a very important detail and you should always share which kind of Docker you are using, otherwise you will get completely different answers. This is how Docker Desktop works, so there is nothing to do . Docker Desktop will share your home with its virtual machine but the owner will be root similarly to rootless containers. I don’t remember how it works on Windows and I can’t check it right now, but this is how it works on macOS and apparently on Linux.

Ok, so if I really was determined to get a least privilege situation going within the container, I would need to uninstall Docker Desktop and any Docker leftovers and re-install a different way? If so, can you point me to some installation instructions for this scenario using ubuntu as a host to Docker engine? If it means uninstalling, I think I will just clone a brand new ubuntu VM image and start from scratch there.

I was wrong to say

The owner will be the user you set for the container, but it looks like the user has to exist in the container which is not necessary for the processes to run as a specific user id. The problem is (on macOS) that it looks like you can’t set the group. This mounting feature of Docker Desktop handles the --user option only when you don’t set the group. But that group definition is not neccessary in your case, as if you set the user, the group will be the already set group of the user. So try --user 1000 only.

Vexing to be sure. Same issue I am afraid.

docker run --rm --name devtest -it --env-file env.list -v /home/user/testshare:/data --user 1000 -p 3000:3000 mycontainer /bin/sh
/app $ id
uid=1000(node) gid=1000(node) groups=1000(node)
/app $ cd /data/
/data $ ls -lah
total 12K
drwxrwxr-x 3 root root 4.0K May 29 08:44 .
drwxr-xr-x 1 root root 4.0K May 29 14:16 ..
drwxrwxr-x 2 root root 4.0K May 29 08:44 DirCreatedByHostUser
-rw-rw-r-- 1 root root    0 May 29 08:44 FileCreatedByHostUser.txt
/data $ touch test
touch: cannot touch 'test': Permission denied

I quickly tested it on Linux. It looks like on Linux it is indeed works as rootless Docker.

So you need to confiugure the subuid and subgid files and if the lowest userid after the username is 100000, then it is UID 1 inside the container. 100001 is UID 2 and so on. On the host, you have to change the owner of the files and folders to 100100 for example (depends on your subuid setting), because if your user is the owner, that is root inside the container. If any other user which is not in the range of allowed UIDs, it will be nobody in the container.

Or… You could simply install Docker CE instead of the desktop and you can be sure that UIDs inside the container will be the same as outside.

Sounds like Docker CE would be an easier path? The installation notes for rootless seem quite long with lots of unfamiliar things for me.

Really what I want is to have most of the Docker environment as you might expect in a large organisation such that I can learn what to expect as I progress in my home study of Docker. In large organisations, they prefer “least privilege” for production environments. So I would expect that I would need my home Docker environment to be able to support a “least privilege” mode for my test projects. Which means I will then be equipped to deal with “least privilege” for deploying to production environments in large organisations. For that, do you recommend Docker CE as the best/easiest path for me personally at home? Also, do you have a link to help installing Docker CE?

Finding the installation page should be easy by searching for “Docker CE install” on a search engine, so sorry for letting you find it :slight_smile: But let us know if you can’t. Make sure you find it in the official documentation on docs.docker.com where you probably followed the instructions to install Docker Desktop.

Whether you install Docker CE or Docker Desktop is up to you. On Linux, you don’t need Docker Desktop, unless you need its features. In a production environment you will always have Docker CE, not Docker Desktop. Docker Desktop is for development and can make some things easier but it is a complex system and can also make some things harder or impossible. You will probably learn about both eventually, but start with the one you feel easier and remember that in production, in large companies you will always need to understand Docker CE and other kind of containers natively running on Linux or Windows, but more often on Linux. Docker Desktop also works slightly differently on each platform, because it has to to be compatible with the host.

I would also note that regardless of what you choose, you will need to understand Linux and how it handles permissions because that is the key, Docker just uses it, doesn’t invent it…

1 Like

Thanks for your help. Always more stuff for me to learn.

Thanks again. After removing Docker Desktop completely and installing Docker Engine/CE, it all now works as expected. Here is a listing of the externally hosted “~/testshare” directory, showing up as the container directory path “/data”:

/data $ ls -lah
total 16K
drwxrwxr-x 4 node node 4.0K May 30 13:07 .
drwxr-xr-x 1 root root 4.0K May 30 13:05 ..
drwxr-xr-x 2 node node 4.0K May 30 13:06 DirCreatedByContainerUser
drwxrwxr-x 2 node node 4.0K May 29 08:44 DirCreatedByHostUser
-rw-r--r-- 1 node node    0 May 30 13:06 FileCreatedByContainerUser.txt
-rw-rw-r-- 1 node node    0 May 29 08:44 FileCreatedByHostUser.txt

The owner is showing correctly as the container user node (1000:1000) and as this user I created a text file and a subdirectory. It’s all good now. I never would have guessed that the root cause of all of this is my use of Docker Desktop (which on your docs says it includes Docker Engine). And that the remediation was to only install Docker Engine on it’s own. That just never would have made sense to me - and still doesn’t really.