How can I run stock images as an alternate user?

I’d like to allow my users to run Docker containers as themselves on some multi-user systems.

The usual answer to this question is to use docker container run --user foo. However, the --user flag by itself doesn’t work for stock images because the user doesn’t exist in the image:

$ docker run --user stefan httpd
docker: Error response from daemon: linux spec user: unable to find user stefan: no matching entries in passwd file.
ERRO[0000] error waiting for container: context canceled 

The solution we’ve come up with so far is to create a custom image which extends a base image (e.g. FROM:httpd), add a user, and grant that user permissions to do things inside a container.

But this isn’t a sustainable solution, as we would need to do this for every user that wants to run the image, and then maintain (and update) a bunch of custom images for the foreseeable future. It’s also difficult to investigate each image to discover exactly what needs to be changed for a non-root user to work.

Is there a way to add a user to a container upon instantiation, so that the --user flag will work?

I could see doing this using an Entrypoint script, but it seems that the --user flag will be applied before the Entrypoint script is run.

Instead of using a username you can just specify its UID instead. This will make linux run as that user id without having to check if it exists or not.

Of course, any files that it interacts with will need to be writable by that user. But as an example, the following worked for me:

I’m in a blank directory which I created and only I have write access to:

$ ls -la
total 0
drwxr-xr-x 2 emily emily 60 Jan 20 10:25 .
drwxr-xr-x 3 emily emily 60 Jan 20 10:24 ..

Run a container which creates a file in the current directory (bind mounded into the container). Use the id command to get my current uid and gid to use:

 $ docker run -v $(pwd):/data -u $(id -u):$(id -g) alpine touch /data/test

Confirm that the permissions have worked as expected on my host machine:

$ ls -la
total 0
drwxr-xr-x 2 emily emily 60 Jan 20 10:25 .
drwxr-xr-x 3 emily emily 60 Jan 20 10:24 ..
-rw-r--r-- 1 emily emily  0 Jan 20 10:25 test
1 Like

Thanks. That does help a bit. However, as you pointed out, I still need to grant permissions to that user to do certain things. In the httpd container, for example, I would need to grant access to the Logs directory and the ability to bind to port 80:

$ docker run --user 1000 httpd
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.3. Set the 'ServerName' directive globally to suppress this message
(13)Permission denied: AH00072: make_sock: could not bind to address 0.0.0.0:80
no listening sockets available, shutting down
AH00015: Unable to open logs
$

Is there a way to grant this access upon instantiation, instead of creating a new image which adds all of these permissions? Perhaps through an entrypoint script?

I may have found a good middle solution.

Since @emilyshepherd pointed out that I can simply specify a UID instead of a Username, I have much more flexibility here.

The following image:

# NAME:http-simple
# Run an httpd image, but allow `docker run --user SOMEUSER:www-data ...`
FROM httpd:latest

# httpd writes it's PID file to /usr/local/apache2/log. Here, we change
# the ownership/perms to allow group www-data to write the PID to that directory.
# The image is then started with `--user SOMEUSER:www-data' and can write the PID.
RUN chmod g+w /usr/local/apache2/logs

# Allow a non-privileged user to start httpd & bind to port 80 ( < 1024)
# per https://wiki.apache.org/httpd/NonRootPortBinding
RUN setcap cap_net_bind_service=+ep /usr/local/apache2/bin/httpd

will allow me run as an arbitrary user, as long as I include the ‘www-data’ group:

$ docker run --rm -p 80:80 --user 1000:www-data httpd-simple
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.3. Set the 'ServerName' directive globally to suppress this message
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.3. Set the 'ServerName' directive globally to suppress this message
[Tue Jan 23 00:34:41.764785 2018] [mpm_event:notice] [pid 1:tid 140393785649024] AH00489: Apache/2.4.29 (Unix) configured -- resuming normal operations
[Tue Jan 23 00:34:41.764897 2018] [core:notice] [pid 1:tid 140393785649024] AH00094: Command line: 'httpd -D FOREGROUND'
172.17.0.1 - - [23/Jan/2018:00:34:49 +0000] "GET / HTTP/1.1" 304 -

It sounds like you found a solution so I’m not sure if this helps much but,… I solved a similar issue using a wrapper script to create the container and run adduser in it. Then I attach to the container as the user. The script actually grabs the UID and first GID from the user running it so it is generic. You can have a look here if you are interested.