Securing Docker containers UID/GID?

Guys i have very basic question on docker security.I’m trying to understand security from both the host/container(s). I’ve had limited docker experience but i haven’t seen anyone bother to pass the uid/gid when starting a container. My understanding if you don’t then you will be running as root from both the host and container perspective. From the container perspective i think this could be a major security concern and you should always be running as a standard user & always pass a uid/gid when you start your containers. Correct me if i’m wrong.

From the host perspective i would assume the only concern would be your mounted volumes otherwise you should be fine from host perspective unless our course there was a security type vulnerability and they were about to break out of the container.

With all that being said would best practices dictate that each container, in general, should be started with a unique UID? Not sure about GID. Should you create a unique user for each type of container you will be running?

  • Some containers start as root and execute the main process as root.
  • Some containers start as root, but execute the main process as an unprivileged user. Usually those images provide env variables for user mapping and apply the provided IDs to the unprivileged user, then chown files in the volume mount locations and start the main process.
  • Some containers already start as an unprivileged user. These have a USER instruction in the Dockerfile. See Docker run reference | Docker Documentation for how to override the UID:GID when creating the container (the same can be done in a compose file).

In case 2 and 3, the UID inside the container does not necessarily require having a user with the same UID on the host.

1 Like

Thanks, If you wouldn’t mind i would like to dig a deeper.

On #1 i would assume this is when you either don’t have a user (UID/GID) defined in your Dockerfile & you don’t pass the UID/GID in during docker run?
ex

docker run UID=1000 GID=1000 ....

This i would assume would be a big security issue granted docker run will run the container as unprivileged by default but i don’t know how much of a panacea that is. Honestly in production this is what i’ve seen in every case.

#2 would be when we pass in the UID/GID to the docker run command like above

#3 would be when we modify user parameters when we build the docker image (Dockerfile):

RUN groupadd -g 1000 -o MyTempUser
RUN useradd -m -u 1000 -g 1000 -o -s /bin/bash MyTempUser
USER MyTempUser

i would assume you would always want to run like #2 & #3 (for security sake) and it would be rare you would ever (if ever) run like #1? With that being said should you probably create a unique user at the host level for each “type” of docker container you will be running? Not sure how much running in unprivileged will save you even if your running root/root (#1).

Please don’t just set the UID/GID as environment, as it bypasses mechanisms introduced by the image maintainer (case 2) or docker (case 3).

Case 1)
Build image without USER instruction:

cat <<EOF  | docker build -t test:root -
FROM alpine:3.16.3
CMD ["id"]
EOF

Run container without --user argument:

$ docker run -ti --rm test:root
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

The user is root with stripped down capabilities in the isolated container. If someone mounts the /etc folder inside the folder, that person could modify files in the container, but the same person would not be able to mount a remote share into the container. Restrictions on file access could be mitigated by using Isolate containers with a user namespace | Docker Documentation, which effectively maps an unprivileged host user as root user inside the container.

Case 3)
Build image with USER instruction:

cat <<EOF  | docker build -t test:me -
FROM alpine:3.16.3
RUN adduser -u 783 -S me -G users 
USER me
ENTRYPOINT ["id"]
EOF

Run container without --user argument:

$  docker run -ti --rm test:me
uid=783(me) gid=100(users) groups=100(users)

Run container with --user argument to override the user from the USER instruction:

$  docker run -ti --rm --user 1000:1000 test:me
uid=1000 gid=1000

Actually the --user argument seem to not care about the USER instruction from the image at all. Thus, the argument could be applied to an image of case 1 as well.

Case 2) adds a user, but does not use the USER instruction. Instead, an entry point script expect variables following the naming convention *_UID and *_GID (where * is an arbitrary string), chowns the files in volumes and exec’s the main process using that UID/GID. Those images will fail if the --user argument is passed. You can check any linuxserver.io image to see how it’s used and implemented.

While home users prefer type 2 images (after all it’s convinient to have the files chowned and sometimes even chmodded for you), enterprise users usualy use type 3 images.

If you meet a type1 image, the service inside the container either really requires root permissions (and then usually need --cap_add to add required capabilities as well) OR is a good candidate for refactoring to one of the other types.