alkalinity
alkalinity

Reputation: 2030

Share files between host system and docker container using specific UID

I'm trying to share files within a Docker guest using the volume sharing. In order to get the same UID, and therefore interoperability with those files, I would like to create a user in the Docker guest with the same UID as my own user.

In order to test out the idea, I wrote the following simplistic Dockerfile:

FROM phusion/baseimage

RUN touch /root/uid-$UID

Testing it with docker build -t=docktest . and then docker run docktest ls -al /root reveals that the file is simply named uid-.

Is there a means to share host environment variables with Docker during the guest build process?

Upvotes: 22

Views: 14061

Answers (5)

Koudela
Koudela

Reputation: 181

Although there is an accepted answer it does not work for me. I needed the same uid:gid for all the files mounted (with bind) from the host file system in the docker container. It took me nearly 2 days to figure out why docker can't do this and how I can do this nevertheless. I leave it here if someone faces the same issues:

host directory mounts in docker and why they do not work (edit: in docker desktop)

Docker volume/mounted binds have a significant downside: They statically change ownership: user(1000) becomes root(0) and www-data(33) becomes nobody(65534). And they do not allow to change ownership in the container. Thus access conflicts are mandatory.

This is known as user namespacing. It is a security feature to prevent privilege-escalation attacks. It can be disabled for containers via the --userns=host. However:

There is a side effect when using this flag: user remapping will not be enabled for that container but, because the read-only (image) layers are shared between containers, ownership of the containers filesystem will still be remapped.

direct quote from the docker user manual

edit 24.03.24

The wrong mapping occurs on docker desktop only (at least at docker desktop version 4.13.1 (90346)). If I use the docker daemon only, the mapping is always correct.

how host directory mounts work (with sshfs)

To circumvent this issue we can use battle proven linux server mounting programms like sshfs.

First we need to access our host machine from the container via ssh. Therefore, an ssh server has to be installed on the host (with passwd or ssh-keys configured) and an ssh client on the container.

Host:

apt-get update

apt-get install openssh-server -y

Container:

apt-get update

apt-get install ssh sshfs -y

First we check in the container if the ssh connection is working via ssh [email protected].

If it does we configure in the container a sshfs mount.

sshfs -o allow_other,IdentityFile=/home/$(whoami)/.ssh/id_rsa [email protected]:/host/source/path /container/target/path

If you do not want to supply your password you can leave the containers public ssh key on the host e.g. via ssh-copy-id. Then:

sshfs -o allow_other,IdentityFile=/home/$(whoami)/.ssh/id_rsa [email protected]:/host/source/path /container/target/path

The sync is almost instant. And the permission mapping is correct even if you create files from within the container.

Hint: We need to run container with the --privileged flag.

security aspects (warning)

Since we have to run the container in the privileged mode, root in the container is the same as root on the host. The same applies for every other user.

Thus, for everything we run in that container we have to put up the same security measures as it would run directly on the host.

We have to run an ssh-server on the host, which has (of course) to be targeted by security measures.

Best practice: Use mounting of host directories for development only.

Upvotes: 0

Dalzhim
Dalzhim

Reputation: 2028

While researching a solution to this problem, I have found the following article to be a great resource: https://medium.com/@mccode/understanding-how-uid-and-gid-work-in-docker-containers-c37a01d01cf

In my scripts, the solution boiled down to the following :

docker run --user $(id -u):$(id -g) -v /hostdirectory:/containerdirectory -v /etc/passwd:/etc/passwd myimage

Of course, id -u can be replaced by other means of retrieving a user's gid, such as stat -c "%u" /somepath

Upvotes: 28

ISanych
ISanych

Reputation: 22680

The environment is not shared, you could use -e, --env options to set env variables in container.

I usually use this approach when I want to have the same owner of the mapped volume: I check uid & gid of directory in container and then create a corresponding user. Here my script (setuser.sh) which creates a user for a directory:

#!/bin/bash

setuser() {
  if [ -z "$1" ]; then
    echo "Usage: $0 <path>"
    return
  fi
  CURRENT_UID=`id -u`
  DEST_UID=`stat -c "%u" $1`
  if [ $CURRENT_UID = $DEST_UID ]; then
    return
  fi
  DEST_GID=`stat -c "%g" $1`
  if [ -e /home/$DEST_UID ]; then
    return
  fi
  groupadd -g $DEST_GID $DEST_GID
  useradd -u $DEST_UID -g $DEST_GID $DEST_UID
  mkdir -p /home/$DEST_UID
  chown $DEST_UID:$DEST_GID /home/$DEST_UID
}
setuser $1

And this is the wrapper script which runs commands as the user, where the directory with permissions is specified either as $USER_DIR or in /etc/user_dir

#!/bin/bash
if [ -z "$USER_DIR" ]; then
  if [ -e /etc/user_dir ]; then
    export USER_DIR=`head -n 1 /etc/user_dir`
  fi
fi
if [ -n "$USER_DIR" ]; then
  if [ ! -d "$USER_DIR" ]; then
    echo "Please mount $USER_DIR before running this script"
    exit 1
  fi
  . `dirname $BASH_SOURCE`/setuser.sh $USER_DIR
fi
if [ -n "$USER_DIR" ]; then
  cd $USER_DIR
fi
if [ -e /etc/user_script ]; then
  . /etc/user_script
fi
if [ $CURRENT_UID = $DEST_UID ]; then
  "$@"
else
  su $DEST_UID -p -c "$@"
fi

P.S. Alleo suggested different approach: to map users and groups files into container and to specify uid and gid. So your container does not depend on built-in users/groups you could use it without additional scripts.

Upvotes: 11

mixel
mixel

Reputation: 25846

I slightly modified @ISanych answer:

#!/usr/bin/env bash

user_exists() {
  id -u $1 > /dev/null 2>&1
}

group_exists() {
  id -g $1 > /dev/null 2>&1
}

setuser() {
  if [[ "$#" != 3 ]]; then
    echo "Usage: $0 <path> <user> <group>"
    return
  fi
  local dest_uid=$(stat -c "%u" $1)
  local dest_gid=$(stat -c "%g" $1)
  if user_exists $dest_uid; then
    id -nu $dest_uid
    return
  fi
  local dest_user=$2
  local dest_group=$3

  if user_exists $dest_user; then
    userdel $dest_user
  fi

  if group_exists $dest_group; then
    groupdel $dest_user
  fi

  groupadd -g $dest_gid $dest_group
  useradd -u $dest_uid -g $dest_gid -s $DEFAULT_SHELL -d $DEFAULT_HOME -G root $dest_user
  chown -R $dest_uid:$dest_gid $DEFAULT_HOME
  id -nu $dest_user
}

REAL_USER=$(setuser $SRC_DIR $DEFAULT_USER $DEFAULT_GROUP)

setuser function accepts user and group names that you want to assign to uid and gid of provided directory. Then if user with such uid exists then it simply returns login corresponding to this uid, otherwise it creates user and group and returns login originally passed to function.

So you get the login of user that owns destination directory.

Upvotes: 1

Usman Ismail
Usman Ismail

Reputation: 18669

This is not possible and will probably never be possible because of the design philosophy of keeping builds independent of machines. Issue 6822.

Upvotes: 1

Related Questions