Typically, permissions issues with a host volume mount are because the UID/GID inside the container does not have access to the file according to the UID/GID permissions of the file on the host. However, this specific case is different.
The dot at the end of the permission string, drwxr-xr-x.
, indicates SELinux is configured. When using a host mount with SELinux, you need to pass an extra option to the end of the volume definition:
- The
z
option indicates that the bind mount content is shared among multiple containers.
- The
Z
option indicates that the bind mount content is private and unshared.
Your volume mount command would then look like:
sudo docker run -i -v /data1/Downloads:/Downloads:z ubuntu bash
See more about host mounts with SELinux at Configure the selinux label.
For others that see this issue with containers running as a different user, you need to ensure the UID/GID of the user inside the container has permissions to the file on the host. On production servers, this is often done by controlling the UID/GID in the image build process to match a UID/GID on the host that has access to the files (or even better, do not use host mounts in production).
A named volume is often preferred to host mounts because it will initialize the volume directory from the image directory, including any file ownership and permissions. This happens when the volume is empty and the container is created with the named volume.
macOS users now have OSXFS which handles UID/GIDs automatically between the Mac host and containers. One place it doesn't help with are files from inside the embedded VM that get mounted into the container, like /var/lib/docker.sock.
For development environments where the host UID/GID may change per developer, my preferred solution is to start the container with an entrypoint running as root, fix the UID/GID of the user inside the container to match the host volume UID/GID, and then use gosu
to drop from root to the container user to run the application inside the container. The important script for this is fix-perms
in my base image scripts, which can be found at: Docker Base Images from Brandon Mitchell
The important bit from the fix-perms
script is:
# Update the UID
if [ -n "$opt_u" ]; then
OLD_UID=$(getent passwd "${opt_u}" | cut -f3 -d:)
NEW_UID=$(stat -c "%u" "$1")
if [ "$OLD_UID" != "$NEW_UID" ]; then
echo "Changing UID of $opt_u from $OLD_UID to $NEW_UID"
usermod -u "$NEW_UID" -o "$opt_u"
if [ -n "$opt_r" ]; then
find / -xdev -user "$OLD_UID" -exec chown -h "$opt_u" {} \;
fi
fi
fi
That gets the UID of the user inside the container, and the UID of the file, and if they do not match, calls usermod
to adjust the UID. Lastly it does a recursive find to fix any files which have not changed UIDs. I like this better than running a container with a -u $(id -u):$(id -g)
flag because the above entrypoint code doesn't require each developer to run a script to start the container, and any files outside of the volume that are owned by the user will have their permissions corrected.
You can also have Docker initialize a host directory from an image by using a named volume that performs a bind mount. This directory must already exist, and you need to provide an absolute path to the host directory, unlike host volumes in a compose file which can be relative paths. The directory must also be empty for Docker to initialize it. Three different options for defining a named volume to a bind mount look like:
# create the volume in advance
$ docker volume create --driver local \
--opt type=none \
--opt device=/home/user/test \
--opt o=bind \
test_vol
# create on the fly with --mount
$ docker run -it --rm \
--mount type=volume,dst=/container/path,volume-driver=local,volume-opt=type=none,volume-opt=o=bind,volume-opt=device=/home/user/test \
foo
# inside a docker-compose file
...
volumes:
bind-test:
driver: local
driver_opts:
type: none
o: bind
device: /home/user/test
...
Lastly, if you try using user namespaces, you'll find that host volumes have permission issues because UID/GIDs of the containers are shifted. In that scenario, it's probably easiest to avoid host volumes and only use named volumes.