Hot to mount local directories as a non-root user in a container

I’ve read quite a few threads on the internet about how to best mount local (project) directories into a Docker container so that the directories are not owned by the root user. Unfortunately, I’ve not found a precise answer.

I’m building my development stack with this docker-compose.yml (SfDocker) file:

db:
    image: mysql:latest
    ports:
    - "3306:3306"
    environment:
        MYSQL_ROOT_PASSWORD: symfonyrootpass
        MYSQL_DATABASE: symfony
        MYSQL_USER: symfony
        MYSQL_PASSWORD: symfonypass
worker:
    image: symfony/worker-dev
    ports:
    - "8080:80"
    environment:
        XDEBUG_HOST: 192.168.1.194
        XDEBUG_PORT: 9000
        XDEBUG_REMOTE_MODE: req
    links:
    - db
    volumes:
    - "var/nginx/:/var/log/nginx"
    - symfony-code:/var/www/app

Volumes are mounted at runtime only after the images are built. I’ve added a new user by RUN groupadd -r luqo33 && useradd -r -g luqo33 luqo33 in the symfony/worker-dev image, but I was not able to chmod the mounted volumes so that it is owned by luqo33:www-data. I’ve tried to do it by:

  1. Copying and running an entrypoint.sh with chmod command:
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

The container would start and then shut down with no apparent reason.

  1. Executing CMD chown -R luqo33:www-data while starting containers - this could not work because at the time of starting the worker-dev container, the volumes seem not to be mounted yet.

I did not manage to set the ownership of the mounted directories to users other than root. How can I achieve this?

Properly implementing computer security requires expertise beyond my limited experience. Therefore, what’s provided below outlines an understanding of standard Linux DAC permissions, when applied to container processes, as gleaned through my experimentation (Ubuntu 12.04 both host & container) and isn’t intended to be an authoritative source. Also, there are a number of other caveats that affect the outcome of the examples/explanations below, from various standard DAC configuration settings, like setuid, to extended/enhanced security schemes offered by SElinux, Extended DAC, POSIX ACL… .

The discussion has been organized into two topics:

Interaction between Host DAC & Container Process Permissions
Dockerfile USER differs from Linux login

Interaction between Host DAC & Container Process Permissions

It’s been my experience that the Host OS decides to either grant or deny a container’s process access to the host directory/file after considering the DAC settings specified to protect the directories/files and the User ID (UID) and Group membership IDs (GID) associated to the container’s process. Note, in general a process inherits the UID and GID list of the user that started it, however, certain Linux security settings can direct a process to inherit the permissions from the UID/GID of the initiating file, instead of the user who started the process. Unless otherwise noted, this discussion assumes processes inherit the user’s permission set.

Given this understanding, a non-root user account within a container must include, as either its UID or as elements in its GID list, some combination of the host directory/file Owner ID and/or GID(s) required to properly access host directories/files. Note, the IDs must match, as the same username/groupname within a container will likely have a different ID than the same named one in the host’s OS. Also, the initial permissions, UID & GID list, of the non-root user account defined within the container may require preservation in order to maintain the container’s user account access to, for example, its own home directory within the container whose directory/file permissions were statically established during the container’s creation.

Ex 1:

  • Given host directory DAC: Owner ID: 1010 read,write,execute; GID: 1010 read,write,execute; Other ID: deny all

  • Given container username: containeruser created during docker build with: UID: 1000, GID list: 1000, 121, 122, 123.

  • Given container home directory DAC Owner ID: 1000 read,write,execute; GID: 1000 read,write,execute; Other ID: deny all;

  • Given container process started with containeruser permissions: UID1000; GID list: 1000, 121, 122, 123.

Outcome: The container process would be prevented from accessing the host directory because the process’ UID, fails to match the host directory’s Owner ID, none of the process’ GID list elements match the host directory’s GID, and and any groups besides the host directory’s GID (1010) are denied access by Other ID’s permission settings. However, this same container process would exercise complete control over the containeruser’s home directory and any other container resource configured to allow access by containeruser’s UID/GID list. Any files/directories within the container created by the container process will reflect an Owner ID of 1000 and potentially, but not always, a GID of 1000.

Ex 2:

  • Given host directory DAC: Owner ID: 1010 read,write,execute; GID: 1010 read,write,execute; Other ID: deny all.

  • Given container username: containeruser created during docker build with: UID: 1010; GID list: 1010, 121, 122, 123.

  • Given container directory DAC OwnerID: 1010 read,write,execute; Primary GID: 1010 read,write,execute; Other ID: deny all.

  • Given container process started with containeruser permissions: UID: 1010; GID: 1010, 121, 122, 123.

Outcome: The container process would exercise complete control when accessing both the host directory and the containeruser’s home directory because the UID of the container process matches both the host and container home directory Owner IDs that confer full access to these resources. Also, the container process may manipulate any other container resource configured to allow access by containeruser’s UID/GID list. Any files/directories within the container created by the container process will reflect an Owner ID of 1010 and potentially a Primary GID of 1010. Also, any host files/directories created by the container process will reflect an Owner ID of 1010 and potentially a Primary GID of 1010.

Ex 3:

  • Given host directory DAC: Owner ID: 1010 read,write,execute; GID: 1010 read,write,execute; Other ID: deny all.

  • Given container username: containeruser created during docker build with: UID: 1000; GID list: 1000, 121, 122, 123.

  • Given container directory DAC OwnerID: 1000; GID: 1000; Other ID deny all.

  • Given script that runs Linux usermod to ‘normalize’ the container’s non-root account UID and/or GID list, that’s executed within the container by the container’s root account, sometime before starting any process by the non-root account. In this example, the ‘normalize’ operation changes the containeruser UID from 1000 to 1010.

  • Given container process started with ‘normalizedcontaineruser permissions: UID1010; GID list: 1000, 121, 122, 123

Outcome: The container process would exercise complete control when accessing the host directory because the container’s process UID matches the host directory Owner ID that’s been granted full control. The container process would exercise complete control when manipulating the containeruser’s home directory because the container process’ GID list includes GID value 1000 that grants full control. Also, the container process can manipulate any other container resource configured to allow access by containeruser’s ‘normalized’ UID or GID list. Any files/directories within the container created by the container process will reflect an Owner ID of 1010 and potentially a GID of 1000. Also, any host files/directories created by the container process will reflect an Owner ID of 1010 and potentially a GID of 1000.

The situation described by your post may not require the dynamic (run-time) establishment of the containeruser’s UID/GID list, as presented by the example above. The necessary UID/GID values may be assignable during image construction via docker build using usermod. However, you or other readers may be interested in the more dynamic approach. If so, read the following:

  • The script called userUID_GID_Reassign.sh 'normalize’s permission settings. It can, when executed within a container by the container’s root account, adapt an existing non-root container account by selectively modifying the UID of the non-root account and potentially its GID list. When ‘normalizing’ the GID list, this script performs a set union operation to preserve, as much as possible, the containeruser’s permission set, and by including specified host GIDs, broaden its ability to manipulate host directories/files mounted into the container’s file system. Please note, the union operator extends the permissions of containeruser allowing this account to potentially access unintended resources residing both internally: to the container, as well as externally: to file system objects exposed by the host directory mounted to the container’s file system. Also, very important, since this script executes when running the container, its usermod commands will change its home directory’s DAC Owner ID & GID to reflect new values assigned to containeruser. If the host directory mount point occurs within the containeruser’s home directory structure, the Owner ID and potentially the GID of host artifacts accessible via the mounted host directory will reflect the containeruser’s newly assigned UID and Primary GID, as usermod recursively cascades these DAC settings throughout the user’s home directory tree. Changing the host directory DAC settings can be avoided by mounting the host directory to a container directory tree separate from the containeruser’s tree.

####Dockerfile USER differs from Linux login

Another dynamic that’s probably important to your situation, occurs when establishing an account’s proper DAC settings applied to the container’s file system during its start up. There’s at least one and potentially other DAC settings applied, for example to Linux devices, like /dev/console, that are dynamically established by the Linux login process see 9806. Executing the Dockerfile USER command to establish these DAC settings when running docker build does not, at least currently, trigger the execution of this login process when running a container, therefore, certain settings aren’t properly configured as expected, compared to a typical user login experience.

To date, the /dev/console issue described by #9806 was addressed by simply executing login for the non-root account when running a container (docker run). Executing login may address some aspect that currently results in the failure described by your post. However, there are other solutions, for example, it should be possible to dispense with running the Linux login process, during docker run|start if static and exact DAC permission settings are known and can be applied to the container’s file system during docker build. However, if you choose to explore a solution involving login, when running a container, be aware that login must be executed with the container’s root account. This typically requires the Dockerfile ENTRYPOINT statement to be performed while the Dockerfile USER is implicitly/explicitly specified as ‘root’. Also, the root user context can be established when starting a container using docker run -u option.

1 Like