Different File Owner Inside Docker Container and in Host MAChine

Different file owner inside Docker container and in host machine

Filesystems, at least in Unix- and Linux-like systems (including macOS), file owners are a number, not a name. Various tools such as ls will translate the number into a name for convenience, but it is still just a number. Your user gitlab-runner in the container, and the user roggerfernandes on the host system, have the same UID. You can find the numeric ID by running the id command.

Here it is on my laptop (reformatted a bit for readability):

$ id
uid=501(dan) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),
79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),501(access_bpf),
33(_appstore),100(_lpoperator),204(_developer),395(com.apple.access_ftp),
398(com.apple.access_screensharing),399(com.apple.access_ssh)

Here you see at the beginning my UID is 501.

You can also run this command with a username, e.g. id gitlab-runner inside the container.

docker exec testes_cashlink id gitlab-runner

So when the user in the container owns a file, it is stored as a numeric ID (quite likely 1000, a common default). When you look on your host system, the mechanism that translates the number into a username just has a different username in its result than you would see inside the container.

If you need a specific user ID inside the container, you need to modify your Dockerfile so that when creating the user, you specify its uid. For example:

RUN useradd -u 1005 <other options> gitlab-runner

Understanding user file ownership in docker: how to avoid changing permissions of linked volumes

Is that correct? Can someone point me to documentation of this, I'm just conjecturing based on the above experiment.

Perhaps this is just because they both have the same numerical value on the kernel, and if I tested on a system where my home user was not id 1000 then permissions would get changed in every case?

Have a read of info coreutils 'chown invocation', that might give you a better idea of how file permissions / ownership works.

Basically, though, each file on your machine has a set of bits tacked on to it that defines its permissions and ownership. When you chown a file, you're just setting these bits.

When you chown a file to a particular user/group using the username or group name, chown will look in /etc/passwd for the username and /etc/group for the group to attempt to map the name to an ID. If the username / group name doesn't exist in those files, chown will fail.

root@dc3070f25a13:/test# touch test
root@dc3070f25a13:/test# ll
total 8
drwxr-xr-x 2 root root 4096 Oct 22 18:15 ./
drwxr-xr-x 22 root root 4096 Oct 22 18:15 ../
-rw-r--r-- 1 root root 0 Oct 22 18:15 test
root@dc3070f25a13:/test# chown test:test test
chown: invalid user: 'test:test'

However, you can chown a file using IDs to whatever you want (within some upper positive integer bounds, of course), whether there is a user / group that exists with those IDs on your machine or not.

root@dc3070f25a13:/test# chown 5000:5000 test
root@dc3070f25a13:/test# ll
total 8
drwxr-xr-x 2 root root 4096 Oct 22 18:15 ./
drwxr-xr-x 22 root root 4096 Oct 22 18:15 ../
-rw-r--r-- 1 5000 5000 0 Oct 22 18:15 test

The UID and GID bits are set on the file itself, so when you mount those files inside your docker container, the file has the same owner / group UID as it does on the host, but is now mapped to /etc/passwd in the container, which is probably going to be a different user unless it's owned by root (UID 0).

The real question is, of course, 'what do I do about this?' If bob is logged in as bob on the given host machine, he should be able to run the container as bob and not have file permissions altered under his host account. As it stands, he actually needs to run the container as user docker to avoid having his account altered.

It seems like, with your current set-up, you'll need to make sure your UIDs > usernames in /etc/passwd on your host match up to your UIDs > usernames in your containers /etc/passwd if you want to interact with your mounted user directory as the same user that's logged in on the host.

You can create a user with a specific user id with useradd -u xxxx. Buuuut, that does seem like a messy solution...

You might have to come up with a solution that doesn't mount a host users home directory.

Wrong OWNER USER on folder/file: docker run -v host_path_dir_file : docker_some_path_dir_file / not working for user defined in Dockerfile

User gigauser numeric ID is not 1000, i.e. 21520. It works on another host because there, local user probably has the numeric ID 1000.

Because we're mounting the folder not copying it, When you mount it, it gets shared into the container with exactly the same permissions/IDs as set on the host - because it's on the host. Containers aren't like VMs with totally separate resources, and even on a VM if you mount something like an NFS directory you'll get numeric IDs that may or may not match your local IDs.

Using /etc/subuid requires passing a flag to the run command, and you'd have to do maths to work out the offsets for your user.

Files owner inside Docker is root, service user is not root. Trouble with permissions

I will answer my own question here, it turns out this was a bug of some software on my freshly installed test machine - most probably Docker. I spent too much time to care, it works everywhere but on this specific rig. <rant> so screw it and actually screw docker. After two years with it - just using for developer setups - I'm under the impression that each machine a dockerized app runs on - needs some special tweaking. </rant>

In several other machines everything works as expected: the user: directive in the yaml correctly assigns the user that the container runs as. The guide linked inside the question can help, or I did a slightly different approach which works as well:

# docker-compose.yml

services:
php:
build:
context: ./docker/php
args:
DOCKER_UID: ${DOCKER_UID:-1000} # these values must be in ENV, eg .env file
user:
"${DOCKER_UID:-1000}:${DOCKER_GID:-1000}"
# Dockerfile
FROM php:8.1.5-fpm-bullseye

ARG DOCKER_UID

# lots of stuff here

# Create a user for provided UID from the host machine
RUN useradd -u ${DOCKER_UID} -m the-whale
RUN usermod -G www-data,the-whale,root,adm the-whale

Docker unexpectedly modify host file's owner / group

This is (probably) normal.

If you read the documentation on the rocker/rstudio image (the base image of rocker/tidyverse), you can find this sentence :

Custom uid/gid etc is usually only needed when sharing a local volume for a user/group whose id does not match the default (1000:1000). Failing to do this could make files change permissions on the linked volume when accessed from RStudio

So what happens ?

In fact, when you run a Docker image, there is something called user mapping between your host user and the container user. This mechanism takes the container user uid and try to map it with a host user uid if it exists.

A classic example is when you execute a command inside a container as root, you'll see that you have root permission on your host. This is because the root uid is always the same (0).

Now, back to your problem, to see why you're container user is mapped to the cluster user of your host, you just need to get the user uid inside your container :

# inside your container, run the "id" command
user@host: id

This will print the uid/gid of the current user. Now you can search for the user on your host whose id match (inside you /etc/passwd file). You'll probably see the cluster user.

How to resolve this ?

Simple : use another user, or map the container user with another user. You can find some info here.

Also try not to map your home directory if possible (so that the impact is less important when you have this kind of situation)

Allow Docker Container & Host User To Write on Bind Mounted Host Directory

Problem: if I set "ubuntu" as owner, container can't write (using php to write), if I set "nobody" as owner, VSCode SSH can't write. I am finding a way to allow both to write without changing directory owner user again and again, or similar ease.

First, I'd recommend the container image should create a new username for the files inside the container, rather than reusing nobody since that user may also be used for other OS tasks that shouldn't have any special access.

Next, as Triet suggests, an entrypoint that adjusts the container's user/group to match the volume is preferred. My own version of these scripts can be found in this base image that includes a fix-perms script that makes the user id and group id of the container user match the id's of a mounted volume. In particular, the following lines of that script where $opt_u is the container username, $opt_g is the container group name, and $1 is the volume mount location:

# 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

# update the gid
if [ -n "$opt_g" ]; then
OLD_GID=$(getent group "${opt_g}" | cut -f3 -d:)
NEW_GID=$(stat -c "%g" "$1")
if [ "$OLD_GID" != "$NEW_GID" ]; then
echo "Changing GID of $opt_g from $OLD_GID to $NEW_GID"
groupmod -g "$NEW_GID" -o "$opt_g"
if [ -n "$opt_r" ]; then
find / -xdev -group "$OLD_GID" -exec chgrp -h "$opt_g" {} \;
fi
fi
fi

Then I start the container as root, and the container runs the fix-perms script from the entrypoint, followed by a command similar to:

exec gosu ${container_user} ${orig_command}

This replaces the entrypoint that's running as root with the application running as the specified user. I've got more examples of this in:

  • DockerCon presentation
  • Similar SO questions

What I tried: In Container, I added user "nobody" to group "ubuntu".
On host, directory (used as mount) was set "sudo chown -R
ubuntu:ubuntu directory", user "ubuntu" was already added to group
"ubuntu". VSCode did edit, container was unable to edit.

I'd avoid this and create a new user. Nobody is designed to be as unprivileged as possible, so there could be unintended consequences with giving it more access.

Edit: the container already created without Dockerfile also ran and
maybe edited with important changes, so maybe I can't use Dockerfile
or entrypoint.sh way to solve problem. Can It be achieved through
running commands inside container or without creating container again?
This container can be stopped.

This is a pretty big code smell in containers. They should be designed to be ephemeral. If you can't easily replace them, you're missing the ability to upgrade to a newer image, and creating a lot of state drift that you'll eventually need to cleanup. Your changes that should be preserved need to be in a volume. If there are other changes that would be lost when the container is deleted, they will be visible in docker diff and I'd recommend fixing this now rather than increasing the size of the technical debt.

Edit: I am wondering, in Triet Doan's answer, an option is to modify
UID and GID of already created user in the container, will doing this
for the user and group "nobody" can cause any problems inside
container, I am wondering because probably many commands for settings
already executed inside container, files are already edited by php on
mounted directory & container is running for days

I would build a newer image that doesn't depend on this username. Within the container, if there's data you need to preserve, it should be in a volume.

Edit: I found that alpine has no usermod & groupmod.

I use the following in the entrypoint script to install it on the fly, but the shadow package should be included in the image you build rather than doing this on the fly for every new container:

if ! type usermod >/dev/null 2>&1 || \
! type groupmod >/dev/null 2>&1; then
if type apk /dev/null 2>&1; then
echo "Warning: installing shadow, this should be included in your image"
apk add --no-cache shadow
else
echo "Commands usermod and groupmod are required."
exit 1
fi
fi

Docker - mount directory's owner and group

When you mount a volume on linux, the resulting folder in the docker container will get the same rights as the folder on the host. If the folder on the host is owned by root, then it'll be owned by root also inside the docker container.

To fix your problem, you have to change the owner of the $(pwd)/vlc-android to match the user id used in the container (according to the Dockerfile you attached in your question, the UID is 499).

Try to execute this:

sudo chown 499 -R $(pwd)/vlc-android

then restart the container.


EDIT:

Another solution would be, if you're able to rebuild the docker image on the ubuntu server, to regenerate the image to use the folder owner id instead of 499.

You simply have to fetch the folder owner ID (try to avoid the root user):

id $username

and regenerate the docker image using the following command:

USER_ID=1000
docker build \
-t my_new_vlc_androing_thingy \
--build-arg VIDEOLAN_CI_UID=${USER_ID} \
.

and run it with:

docker run --rm \
-w /vlc-android \
-v $(pwd)/vlc-android:/vlc-android \
my_new_vlc_androing_thingy \
bash -c "ls -ld /vlc-android"

File ownership after docker cp

In order to get complete control of file ownership, I used the tar stream feature of docker cp:

If - is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT.

I launch the docker cp process, then stream a tar file to or from the process. As the tar entries go past, I can adjust the ownership and permissions however I like.

Here's a simple example in Python that copies all the files from /outputs in the sandbox1 container to the current directory, excludes the current directory so its permissions don't get changed, and forces all the files to have read/write permissions for the user.

from subprocess import Popen, PIPE, CalledProcessError
import tarfile

def main():
export_args = ['sudo', 'docker', 'cp', 'sandbox1:/outputs/.', '-']
exporter = Popen(export_args, stdout=PIPE)
tar_file = tarfile.open(fileobj=exporter.stdout, mode='r|')
tar_file.extractall('.', members=exclude_root(tar_file))
exporter.wait()
if exporter.returncode:
raise CalledProcessError(exporter.returncode, export_args)

def exclude_root(tarinfos):
print('\nOutputs:')
for tarinfo in tarinfos:
if tarinfo.name != '.':
assert tarinfo.name.startswith('./'), tarinfo.name
print(tarinfo.name[2:])
tarinfo.mode |= 0o600
yield tarinfo

main()


Related Topics



Leave a reply



Submit