Reputation: 4664
I've been playing around with Docker for a while and keep on finding the same issue when dealing with persistent data.
I create my Dockerfile
and expose a volume or use --volumes-from
to mount a host folder inside my container.
What permissions should I apply to the shared volume on the host?
I can think of two options:
So far I've given everyone read/write access, so I can write to the folder from the Docker container.
Map the users from host into the container, so I can assign more granular permissions. Not sure this is possible though and haven't found much about it. So far, all I can do is run the container as some user: docker run -i -t -user="myuser" postgres
, but this user has a different UID than my host myuser
, so permissions do not work. Also, I'm unsure if mapping the users will pose some security risks.
Are there other alternatives?
How are you guys/gals dealing with this issue?
Upvotes: 438
Views: 459640
Reputation: 2654
I finally ended up writing a script that syncs selected user ids and group ids from host to container using usermod and groupmod.
docker compose:
volumes:
- /etc/passwd:/etc/passwd.src:ro
- /etc/group:/etc/group.src:ro
environment:
- host_users=www-data,mysql
- host_groups=www-data,mysql,staff
script:
#!/bin/bash
for user in ${host_users//,/ }; do
echo "syncing user $user"
uid=$(grep "^$user:" /etc/passwd.src | awk -F: '{print $3}')
if [ ! -z "$uid" ]; then
RET=1
while [[ RET -ne 0 ]]; do
usermod -u $uid $user
RET=$?
if [[ RET -eq 4 ]]; then
existing_user=$(id $uid -u)
existing_user_new_id=$(shuf -i 101-498 -n 1)
usermod -u $existing_user_new_id $existing_user
sleep 1
elif [[ RET -ne 0 ]]; then
sleep 5
fi
done
else
echo "syncing user $user, not found in host"
fi
done
for group in ${host_groups//,/ }; do
echo "syncing group $group"
gid=$(grep "^$group:" /etc/group.src | awk -F: '{print $3}')
if [ ! -z "$gid" ]; then
RET=1
while [[ RET -ne 0 ]]; do
groupmod -g $gid $group
RET=$?
if [[ RET -eq 4 ]]; then
existing_group=$(getent group $gid | awk -F: '{print $1}')
existing_group_new_id=$(shuf -i 1-32766 -n 1)
groupmod -g $existing_group_new_id $existing_group
sleep 1
elif [[ RET -ne 0 ]]; then
sleep 5
fi
done
else
echo "syncing group $group, not found in host"
fi
done
Also available here: https://github.com/Varun-garg/docker-sync-ids
Upvotes: 1
Reputation: 969
The same as you, I was looking for a way to map users/groups from host to docker containers and this is the shortest way I've found so far:
version: "3"
services:
my-service:
.....
volumes:
# take uid/gid lists from host
- /etc/passwd:/etc/passwd:ro
- /etc/group:/etc/group:ro
# mount config folder
- path-to-my-configs/my-service:/etc/my-service:ro
.....
This is an extract from my docker-compose.yml.
The idea is to mount (in read-only mode) users/groups lists from the host to the container thus after the container starts up it will have the same uid->username (as well as for groups) matchings with the host. Now you can configure user/group settings for your service inside the container as if it was working on your host system.
When you decide to move your container to another host you just need to change user name in service config file to what you have on that host.
Upvotes: 28
Reputation: 21934
If you are doing this for development, a good solution is to use bindfs
:
bindfs
and map the folder for the host user.Here is how my docker-compose setup looks now:
project:
web/src # Container clones it using init scripts.
web/log
__web__/src # Host user uses this. It's just bindfs mirror.
__web__/log
I have thought about this problem for over a year, and bindfs
is the easiest option I have come across. There are no runtime costs apart from cloning.
Upvotes: 0
Reputation: 1781
My approach is to detect the current UID/GID, then create such user/group inside the container and execute the script under him. As a result, all files he will create will match the user on the host:
# get the location of this script no matter what your current folder is, this might break between shells so make sure you run bash
LOCAL_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
# get current IDs
USER_ID=$(id -u)
GROUP_ID=$(id -g)
echo "Mount $LOCAL_DIR into docker, and match the host IDs ($USER_ID:$GROUP_ID) inside the container."
docker run -v $LOCAL_DIR:/host_mount -i debian:9.4-slim bash -c "set -euo pipefail && groupadd -r -g $GROUP_ID lowprivgroup && useradd -u $USER_ID lowprivuser -g $GROUP_ID && cd /host_mount && su -c ./runMyScriptAsRegularUser.sh lowprivuser"
Upvotes: 7
Reputation: 1326
A very elegant solution can be seen on the official redis image and in general in all official images.
Described in step-by-step process:
As seen on Dockerfile comments:
add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
gosu is an alternative of su
/ sudo
for easy step-down from root user. (Redis is always run with redis
user)
/data
volume and set it as workdirBy configuring the /data volume with the VOLUME /data
command we now have a separate volume that can either be docker volume or bind-mounted to a host dir.
Configuring it as the workdir (WORKDIR /data
) makes it be the default directory where commands are executed from.
This means that all container executions will run through the docker-entrypoint script, and by default the command to be run is redis-server.
docker-entrypoint
is a script that does a simple function: Change ownership of current directory (/data) and step-down from root
to redis
user to run redis-server
. (If the executed command is not redis-server, it will run the command directly.)
This has the following effect
If the /data directory is bind-mounted to the host, the docker-entrypoint will prepare the user permissions before running redis-server under redis
user.
This gives you the ease-of-mind that there is zero-setup in order to run the container under any volume configuration.
Of course if you need to share the volume between different images you need to make sure they use the same userid/groupid otherwise the latest container will hijack the user permissions from the previous one.
Upvotes: 84
Reputation: 174
To share folder between docker host and docker container, try below command
$ docker run -v "$(pwd):$(pwd)" -i -t ubuntu
The -v flag mounts the current working directory into the container. When the host directory of a bind-mounted volume doesn’t exist, Docker will automatically create this directory on the host for you,
However, there are 2 problems we have here:
Solution:
Container: create a user say 'testuser', by default user id will be starting from 1000,
Host: create a group say 'testgroup' with group id 1000, and chown the directory to the new group(testgroup
Upvotes: 0
Reputation: 1988
In my specific case, I was trying to build my node package with the node docker image so that I wouldn't have to install npm on the deployment server. It worked well until, outside out the container and on the host machine, I tried to move a file into the node_modules directory that the node docker image had created, to which I was denied permissions because it was owned by root. I realized that I could work around this by copying the directory out of the container onto the host machine. Via docker docs...
Files copied to the local machine are created with the UID:GID of the user which invoked the docker cp command.
This is the bash code I used to change ownership of the directory created by and within the docker container.
NODE_IMAGE=node_builder
docker run -v $(pwd)/build:/build -w="/build" --name $NODE_IMAGE node:6-slim npm i --production
# node_modules is owned by root, so we need to copy it out
docker cp $NODE_IMAGE:/build/node_modules build/lambda
# you might have issues trying to remove the directory "node_modules" within the shared volume "build", because it is owned by root, so remove the image and its volumes
docker rm -vf $NODE_IMAGE || true
If needed, you can remove the directory with a second docker container.
docker run -v $(pwd)/build:/build -w="/build" --name $RMR_IMAGE node:6-slim rm -r node_modules
Upvotes: 0
Reputation: 19006
Use this image: https://hub.docker.com/r/reduardo7/docker-host-user
Important: this destroys container portability across hosts.
init.sh
#!/bin/bash
if ! getent passwd $DOCKDEV_USER_NAME > /dev/null
then
echo "Creating user $DOCKDEV_USER_NAME:$DOCKDEV_GROUP_NAME"
groupadd --gid $DOCKDEV_GROUP_ID -r $DOCKDEV_GROUP_NAME
useradd --system --uid=$DOCKDEV_USER_ID --gid=$DOCKDEV_GROUP_ID \
--home-dir /home --password $DOCKDEV_USER_NAME $DOCKDEV_USER_NAME
usermod -a -G sudo $DOCKDEV_USER_NAME
chown -R $DOCKDEV_USER_NAME:$DOCKDEV_GROUP_NAME /home
fi
sudo -u $DOCKDEV_USER_NAME bash
Dockerfile
FROM ubuntu:latest
# Volumes
VOLUME ["/home/data"]
# Copy Files
COPY /home/data/init.sh /home
# Init
RUN chmod a+x /home/init.sh
#!/bin/bash
DOCKDEV_VARIABLES=(\
DOCKDEV_USER_NAME=$USERNAME\
DOCKDEV_USER_ID=$UID\
DOCKDEV_GROUP_NAME=$(id -g -n $USERNAME)\
DOCKDEV_GROUP_ID=$(id -g $USERNAME)\
)
cmd="docker run"
if [ ! -z "${DOCKDEV_VARIABLES}" ]; then
for v in ${DOCKDEV_VARIABLES[@]}; do
cmd="${cmd} -e ${v}"
done
fi
# /home/usr/data contains init.sh
$cmd -v /home/usr/data:/home/data -i -t my-image /home/init.sh
docker
sh run.sh
Upvotes: 3
Reputation: 93
If you using Docker Compose, start the container in previleged mode:
wordpress:
image: wordpress:4.5.3
restart: always
ports:
- 8084:80
privileged: true
Upvotes: -5
Reputation: 121
For secure and change root for docker container an docker host try use --uidmap
and --private-uids
options
https://github.com/docker/docker/pull/4572#issuecomment-38400893
Also you may remove several capabilities (--cap-drop
) in docker container for security
http://opensource.com/business/14/9/security-for-docker
UPDATE support should come in docker > 1.7.0
UPDATE Version 1.10.0
(2016-02-04) add --userns-remap
flag
https://github.com/docker/docker/blob/master/CHANGELOG.md#security-2
Upvotes: 5
Reputation: 19665
UPDATE 2016-03-02: As of Docker 1.9.0, Docker has named volumes which replace data-only containers. The answer below, as well as my linked blog post, still has value in the sense of how to think about data inside docker but consider using named volumes to implement the pattern described below rather than data containers.
I believe the canonical way to solve this is by using data-only containers. With this approach, all access to the volume data is via containers that use -volumes-from
the data container, so the host uid/gid doesn't matter.
For example, one use case given in the documentation is backing up a data volume. To do this another container is used to do the backup via tar
, and it too uses -volumes-from
in order to mount the volume. So I think the key point to grok is: rather than thinking about how to get access to the data on the host with the proper permissions, think about how to do whatever you need -- backups, browsing, etc. -- via another container. The containers themselves need to use consistent uid/gids, but they don't need to map to anything on the host, thereby remaining portable.
This is relatively new for me as well but if you have a particular use case feel free to comment and I'll try to expand on the answer.
UPDATE: For the given use case in the comments, you might have an image some/graphite
to run graphite, and an image some/graphitedata
as the data container. So, ignoring ports and such, the Dockerfile
of image some/graphitedata
is something like:
FROM debian:jessie
# add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later
RUN groupadd -r graphite \
&& useradd -r -g graphite graphite
RUN mkdir -p /data/graphite \
&& chown -R graphite:graphite /data/graphite
VOLUME /data/graphite
USER graphite
CMD ["echo", "Data container for graphite"]
Build and create the data container:
docker build -t some/graphitedata Dockerfile
docker run --name graphitedata some/graphitedata
The some/graphite
Dockerfile should also get the same uid/gids, therefore it might look something like this:
FROM debian:jessie
# add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later
RUN groupadd -r graphite \
&& useradd -r -g graphite graphite
# ... graphite installation ...
VOLUME /data/graphite
USER graphite
CMD ["/bin/graphite"]
And it would be run as follows:
docker run --volumes-from=graphitedata some/graphite
Ok, now that gives us our graphite container and associated data-only container with the correct user/group (note you could re-use the some/graphite
container for the data container as well, overriding the entrypoing/cmd when running it, but having them as separate images IMO is clearer).
Now, lets say you want to edit something in the data folder. So rather than bind mounting the volume to the host and editing it there, create a new container to do that job. Lets call it some/graphitetools
. Lets also create the appropriate user/group, just like the some/graphite
image.
FROM debian:jessie
# add our user and group first to make sure their IDs get assigned consistently, regardless of other deps added later
RUN groupadd -r graphite \
&& useradd -r -g graphite graphite
VOLUME /data/graphite
USER graphite
CMD ["/bin/bash"]
You could make this DRY by inheriting from some/graphite
or some/graphitedata
in the Dockerfile, or instead of creating a new image just re-use one of the existing ones (overriding entrypoint/cmd as necessary).
Now, you simply run:
docker run -ti --rm --volumes-from=graphitedata some/graphitetools
and then vi /data/graphite/whatever.txt
. This works perfectly because all the containers have the same graphite user with matching uid/gid.
Since you never mount /data/graphite
from the host, you don't care how the host uid/gid maps to the uid/gid defined inside the graphite
and graphitetools
containers. Those containers can now be deployed to any host, and they will continue to work perfectly.
The neat thing about this is that graphitetools
could have all sorts of useful utilities and scripts, that you can now also deploy in a portable manner.
UPDATE 2: After writing this answer, I decided to write a more complete blog post about this approach. I hope it helps.
UPDATE 3: I corrected this answer and added more specifics. It previously contained some incorrect assumptions about ownership and perms -- the ownership is usually assigned at volume creation time i.e. in the data container, because that is when the volume is created. See this blog. This is not a requirement though -- you can just use the data container as a "reference/handle" and set the ownership/perms in another container via chown in an entrypoint, which ends with gosu to run the command as the correct user. If anyone is interested in this approach, please comment and I can provide links to a sample using this approach.
Upvotes: 174
Reputation: 9436
Try to add a command to Dockerfile
RUN usermod -u 1000 www-data
credits goes to https://github.com/denderello/symfony-docker-example/issues/2#issuecomment-94387272
Upvotes: 21
Reputation: 21582
This is arguably not the best way for most circumstances, but it's not been mentioned yet so perhaps it will help someone.
Bind mount host volume
Host folder FOOBAR is mounted in container /volume/FOOBAR
Modify your container's startup script to find GID of the volume you're interested in
$ TARGET_GID=$(stat -c "%g" /volume/FOOBAR)
Ensure your user belongs to a group with this GID (you may have to create a new group). For this example I'll pretend my software runs as the nobody
user when inside the container, so I want to ensure nobody
belongs to a group with a group id equal to TARGET_GID
EXISTS=$(cat /etc/group | grep $TARGET_GID | wc -l)
# Create new group using target GID and add nobody user
if [ $EXISTS == "0" ]; then
groupadd -g $TARGET_GID tempgroup
usermod -a -G tempgroup nobody
else
# GID exists, find group name and add
GROUP=$(getent group $TARGET_GID | cut -d: -f1)
usermod -a -G $GROUP nobody
fi
I like this because I can easily modify group permissions on my host volumes and know that those updated permissions apply inside the docker container. This happens without any permission or ownership modifications to my host folders/files, which makes me happy.
I don't like this because it assumes there's no danger in adding yourself to an arbitrary groups inside the container that happen to be using a GID you want. It cannot be used with a USER
clause in a Dockerfile (unless that user has root privileges I suppose). Also, it screams hack job ;-)
If you want to be hardcore you can obviously extend this in many ways - e.g. search for all groups on any subfiles, multiple volumes, etc.
Upvotes: 40
Reputation: 1067
Here's an approach that still uses a data-only container but doesn't require it to be synced with the application container (in terms of having the same uid/gid).
Presumably, you want to run some app in the container as a non-root $USER without a login shell.
In the Dockerfile:
RUN useradd -s /bin/false myuser
# Set environment variables
ENV VOLUME_ROOT /data
ENV USER myuser
...
ENTRYPOINT ["./entrypoint.sh"]
Then, in entrypoint.sh:
chown -R $USER:$USER $VOLUME_ROOT
su -s /bin/bash - $USER -c "cd $repo/build; $@"
Upvotes: 5
Reputation: 16732
Ok, this is now being tracked at docker issue #7198
For now, I'm dealing with this using your second option:
Map the users from host into the container
#=======
# Users
#=======
# TODO: Idk how to fix hardcoding uid & gid, specifics to docker host machine
RUN (adduser --system --uid=1000 --gid=1000 \
--home /home/myguestuser --shell /bin/bash myguestuser)
# DIR_HOST and DIR_GUEST belongs to uid:gid 1000:1000
docker run -d -v ${DIR_HOST}:${DIR_GUEST} elgalu/myservice:latest
UPDATE I'm currently more inclined to Hamy answer
Upvotes: 16