94

Docker kind of always had a USER command to run a process as a specific user, but in general a lot of things had to run as ROOT.

I have seen a lot of images that use an ENTRYPOINT with gosu to de-elevate the process to run.

I'm still a bit confused about the need for gosu. Shouldn't USER be enough?

I know quite a bit has changed in terms of security with Docker 1.10, but I'm still not clear about the recommended way to run a process in a docker container.

Can someone explain when I would use gosu vs. USER?

Thanks

EDIT:

The Docker best practice guide is not very clear: It says if the process can run without priviledges, use USER, if you need sudo, you might want to use gosu. That is confusing because one can install all sorts of things as ROOT in the Dockerfile, then create a user and give it proper privileges, then finally switch to that user and run the CMD as that user. So why would we need sudo or gosu then?

MrE
  • 19,584
  • 12
  • 87
  • 105

3 Answers3

75

Dockerfiles are for creating images. I see gosu as more useful as part of a container initialization when you can no longer change users between run commands in your Dockerfile.

After the image is created, something like gosu allows you to drop root permissions at the end of your entrypoint inside of a container. You may initially need root access to do some initialization steps (fixing uid's, host mounted volume permissions, etc). Then once initialized, you run the final service without root privileges and as pid 1 to handle signals cleanly.


Edit: Here's a simple example of using gosu in an image for docker and jenkins: https://github.com/bmitch3020/jenkins-docker

The entrypoint.sh looks up the gid of the /var/lib/docker.sock file and updates the gid of the docker user inside the container to match. This allows the image to be ported to other docker hosts where the gid on the host may differ. Changing the group requires root access inside the container. Had I used USER jenkins in the dockerfile, I would be stuck with the gid of the docker group as defined in the image which wouldn't work if it doesn't match that of the docker host it's running on. But root access can be dropped when running the app which is where gosu comes in.

At the end of the script, the exec call prevents the shell from forking gosu, and instead it replaces pid 1 with that process. Gosu in turn does the same, switching the uid and then exec'ing the jenkins process so that it takes over as pid 1. This allows signals to be handled correctly which would otherwise be ignored by a shell as pid 1.

BMitch
  • 231,797
  • 42
  • 475
  • 450
  • When you run this and the gid of the runner is 0, the `groupmod` command in `entrypoint.sh` fails with `groupmod: GID '0' already exists` and the script keeps running. At that point, the jenkins user cannot run docker in the container. Any suggestions? – Mike D Apr 28 '18 at 15:32
  • In this case, I would build two images. A base image where root is required and a jenkins image the pulls from the first image. You could do a multi-stage build to keep it all wrapped up in a single file. Then you could start the 2nd build with your user. This lets you work out permissions out earlier too. – Michael Noe Feb 14 '19 at 21:19
  • 1
    @MichaelNoe Not sure I'm following the multi-stage example. `gosu` is needed here because the permission and user id changes depend on runtime knowledge that you can't calculate at build time. The same Jenkins image may run on multiple hosts with different docker GID's on the host. – BMitch Feb 14 '19 at 21:54
  • Some arguments should be made to explain why `gosu` was created as a replacement for the standard `su`. My first reaction is that I don't care nor want a go program to replace a standard unix tool. – Johan Boulé Jun 09 '21 at 12:58
  • 1
    @JohanBoulé Does the readme on the gosu repo help? https://github.com/tianon/gosu – BMitch Jun 09 '21 at 13:01
  • @BMitch Thanks for the link. While it gives some (vague) hints that docker adds some new use case for the `su` command, it doesn't IMO justify writing a new tool in a new language instead of adding new command line option to the standard `su` program. `gosu` isn't installed in a "standard" base image. – Johan Boulé Jun 11 '21 at 18:45
  • @JohanBoulé You would need to get all the implementations of `su` to be updated, and then distributed by each of the linux vendors. That also wouldn't help the `scratch` users. Making a new binary that can be downloaded where needed is significantly easier. But don't take my word for it, feel free to get a new option added to `su` so we can stop adding this external dependency. – BMitch Jun 11 '21 at 18:52
  • @BMitch You're right the path to perfection is long :) Still, some day, the standard su should be improved. – Johan Boulé Jun 17 '21 at 17:29
  • @BMitch This works fine but doing a "docker exec -it [CID] bash" results in a bash with root privilege. This would not be the case, if the container was started with a non-root user from the beginning. How do you handle this? – Arber Apr 05 '23 at 07:55
  • 1
    @Arber there's a `-u` option for exec. That works both ways, allowing you to be root in a non-root image, or allowing you to be a user in a root image. You could also configure the bashrc to run gosu on interactive logins. For the most part, I don't fix it, because the gosu is restricting the app, not the developer with exec access. – BMitch Apr 05 '23 at 10:00
15

I am using gosu and entrypoint.sh because I want the user in the container to have the same UID as the user that created the container.

Docker Volumes and Permissions.

The purpose of the container I am creating is for development. I need to build for linux but I still want all the connivence of local (OS X) editing, tools, etc. My keeping the UIDs the same inside and outside the container it keeps the file ownership a lot more sane and prevents some errors (container user cannot edit files in mounted volume, etc)

user2466803
  • 265
  • 4
  • 10
  • 1
    I am not sure I follow here: `gosu` runs in the container; It runs as ROOT impersonating another user. This user had itself to be defined in the image. So how is this related to the user who 'created' the image? I can see that if you create the same user as a local user, and then use gosu to run as this user, this will avoid conflicts with a local mounted volume, but only because the user is the same name. – MrE Jun 20 '16 at 23:01
  • 2
    @MrE: The user does not have to be defined in the image. It can be created with `useradd` inside the container on startup. When username, uid and gid are passed as environment variables into the container, they can be set to match the values on the host. – Roland Weber Sep 06 '16 at 09:01
  • I would add that this answer helps to avoid recursively changing permissions **on volumes mounted from host** (*outside* the container), whose user gets inherited by all the mounted files. This prevents the editing of files, as mentioned in the answer. – **E.g.** this happens when mounting *Kubernetes*'s Pod `volumes.hostPath` on *Minikube*, inheriting UID & GID of the `docker` user from the Virtual Machine, on *macOS* or *Windows*; meehh); using `gosu`, in the `ENTRYPOINT`, helps with that. – Kamafeather Nov 09 '20 at 21:19
3

Advantage of using gosu is also signal handling. You may trap for instance SIGHUP for reloading the process as you would normally achieve via systemctl reload <process> or such.

karlsebal
  • 1,449
  • 17
  • 23
  • 2
    I think this is the purpose of `/sbin/tini` – Mike D Apr 28 '18 at 16:09
  • 2
    This is why `gosu` is better than `su` since it's doing an exec rather than a fork. But it misses the OP's question of why you'd want `gosu` over just setting the `USER` in the Dockerfile. – BMitch Dec 03 '21 at 15:13