This walkthrough examines the USER command, provides best practices, and demonstrates how it works on a host
The USER and
GROUP commands in Docker are used to execute commands
inside a container as a non-root user. If no USER or GROUP is provided, the container runs as root. This document
provides some best practices and things to avoid, and ends with a guided demo for the user to walk through the use of
USER/GROUP to better explain the rationale behind the tips given.
This document is written for Linux containers, and assumes some understanding of the UID/GID system in linux.
-
Use a Non-root User:
- For security reasons, it's recommended to run processes inside a container as a non-root user. If an attacker manages to break out of the application running in the container, they will have limited permissions if the container is running as a non-root user.
-
Specify User by UID and GID:
- Instead of specifying a user by name, use the user's UID and GID. This ensures that the user is consistently identified, even if the container's
/etc/passwdfile changes or is different across distributions.USER 1001:1001
- Instead of specifying a user by name, use the user's UID and GID. This ensures that the user is consistently identified, even if the container's
-
Create a Specific User for the Application:
- If your application requires specific permissions, consider creating a dedicated user for your application in the Dockerfile. This can be done using the
RUNcommand to add the user.RUN useradd -ms /bin/bash myuser USER myuser
- If your application requires specific permissions, consider creating a dedicated user for your application in the Dockerfile. This can be done using the
-
Switch Back to Root for Privileged Operations:
- If you need to perform privileged operations in the Dockerfile after setting a non-root user, you can switch back to the root user and then switch back to the non-root user once those operations are complete.
USER root RUN apt-get update && apt-get install -y some-package USER myuser
- If you need to perform privileged operations in the Dockerfile after setting a non-root user, you can switch back to the root user and then switch back to the non-root user once those operations are complete.
-
Combine USER with WORKDIR:
- If you're setting a non-root user, ensure that the working directory (
WORKDIR) is writable by that user. This avoids potential permission issues.
- If you're setting a non-root user, ensure that the working directory (
-
Avoid Using Root User:
- As mentioned, avoid running containers as the root user unless absolutely necessary. This minimizes the potential damage if the container is compromised.
-
Avoid Hardcoding User Names:
- User names might not be consistent across different base images or distributions. Instead, use UIDs and GIDs.
-
Avoid Ignoring File Permissions:
- When switching to a non-root user, ensure that any files or directories that the application needs to access are accessible by that user. This includes both read and write permissions as necessary.
-
Avoid Not Setting a USER:
- If you don't set a
USERin your Dockerfile, the user will default to root. Always explicitly set a user, even if it's just to make it clear who the container will run as.
- If you don't set a
-
Avoid Setting a User Without Necessary Privileges:
- Ensure that the user you set has the necessary privileges to run the commands in the container. For instance, a non-root user might not have the necessary permissions to bind to ports below 1024.
The following examples walk you through a series of examples to show this in action. We will use two Dockerfiles, with one setting the UID/GID directly and one using the default:
# Use the official Ubuntu image as the base
FROM ubuntu:20.04
# Print the UID and GID
CMD sh -c "echo 'Inside Container:' && echo 'User: $(whoami) UID: $(id -u) GID: $(id -g)'"# Use the official Ubuntu image as the base
FROM ubuntu:20.04
# Create a custom user with UID 1234 and GID 1234
RUN groupadd -g 1234 customgroup && \
useradd -m -u 1234 -g customgroup customuser
# Switch to the custom user
USER customuser
# Print the UID and GID
CMD sh -c "echo 'Inside Container:' && echo 'User: $(whoami) UID: $(id -u) GID: $(id -g)'"$ docker build -t default-user-image -f Dockerfile1 .
[+] Building 0.5s (5/5) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile1 0.0s
=> => transferring dockerfile: 255B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:20.04 0.4s
=> CACHED [1/1] FROM docker.io/library/ubuntu:20.04@sha256:33a5cc25d22c45900796a1aca487ad7a7cb09f09ea00b779e 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:d4f5578181af02f83867694b1f002c742d41187d798ee15258babe513f3ab0fd 0.0s
=> => naming to docker.io/library/default-user-image 0.0s
View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/emarqc9x7zz6qn5zamdx1umo8
What's Next?
View a summary of image vulnerabilities and recommendations → docker scout quickview$ docker build -t custom-user-image -f Dockerfile2 .
[+] Building 0.5s (6/6) FINISHED docker:desktop-linux
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load build definition from Dockerfile2 0.0s
=> => transferring dockerfile: 438B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:20.04 0.4s
=> [1/2] FROM docker.io/library/ubuntu:20.04@sha256:33a5cc25d22c45900796a1aca487ad7a7cb09f09ea00b779e3b2026b 0.0s
=> CACHED [2/2] RUN groupadd -g 1234 customgroup && useradd -m -u 1234 -g customgroup customuser 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:312632c54afc34dac90543afefa6f15b3c70244ddf8c0038954b9256742919ba 0.0s
=> => naming to docker.io/library/custom-user-image 0.0s
View build details: docker-desktop://dashboard/build/desktop-linux/desktop-linux/pr185jmyxf0amy0ukfsq75znm
What's Next?
View a summary of image vulnerabilities and recommendations → docker scout quickviewFirst we will pull our current UID/GID from the system using the id command. I'm UID 503 with a primary GID of 20:
$ id
uid=503(jschmidt) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),98(_lpadmin),702(com.apple.sharepoint.group.2),701(com.apple.sharepoint.group.1),33(_appstore),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae)Let's run our first image, the one that does not provide a USER command. As you can see, the UID and GID are 0/0, so the superuser (root). There are two things at work here; first, we are not defining a UID/GID in the Dockerfile so Docker defaults to the superuser. But how does it become superuser if my account is not a superuser account? This is because the Docker engine runs with root permissions, so containers that are built to run as root inherit the permissions from the Docker engine.
$ docker run --rm default-user-image
Inside Container:
User: root UID: 0 GID: 0Let's try and fix this - we really don't want containers running as root. So in this version we explicitly set the UID and GID for the User and Group. Running this container, we see that our user is set appropriately.
$ docker run --rm custom-user-image
Inside Container:
User: customuser UID: 1234 GID: 1234We used a UID and GID that we made up for this container, but does that exist on the host and if not does it matter? A check of the host shows that the UID 1234 does not exist, so we do not have to match on the host.
$ id 1234
id: 1234: no such userAt this point, users of Docker Desktop will notice the flaw in my approach here. I'm interacting directly with the host OS, but for users of Docker Desktop the containers are actually being run in a VM! So, I need to get into that VM to validate all of my findings above.
We will use the nsenter utility image to enter the VM using the following command, which will give us an interactive shell. Note that there are security implications around being able to do this; Docker's Hardened Docker Desktop and Enhanced Container Isolation can be used to mitigate this risk on developer system. Since this is a demo, we are running Docker without any security precautions.
$ docker run -it --rm --privileged --pid=host justincormack/nsenter1
# id 1234
id: unknown user 1234So we can see from the above that user 1234 does not exist in the Docker VM. The VM runs as it's own process via Apple's Hyperkit:
$ ps -ef | grep com.docker.virtualization
503 70764 44911 0 Thu04PM ?? 0:00.89 /Applications/Docker.app/Contents/MacOS/com.docker.virtualization --kernel /Applications/Docker.app/Contents/Resources/linuxkit/kernel --cmdline vsyscall=emulate panic=1 nospec_store_bypass_disable noibrs noibpb no_stf_barrier mitigations=off linuxkit.unified_cgroup_hierarchy=1 console=hvc0 irqaffinity=0 virtio_net.disable_csum=1 eth0.IPNet=192.168.65.3/24 eth0.router=192.168.65.1 eth1.dhcp vpnkit.connect=connect://2/1999 vpnkit.disable=osxfs-data --ramdisk /Applications/Docker.app/Contents/Resources/linuxkit/initrd.img --disk /Users/jschmidt/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw --networkType gvisor --macaddr-filename-prefix vms/0/macaddr --cpus 13 --memoryMiB 8092 --console-log log/vm/console.log --console /Users/jschmidt/Library/Containers/com.docker.docker/Data/vms/0/console.sock --api /Users/jschmidt/Library/Containers/com.docker.docker/Data/virtualization.sock --watchdog --kernel-for-udp --virtiofs /Users --virtiofs /Volumes --virtiofs /private --virtiofs /tmp --virtiofs /var/folders --rosetta
503 65010 3992 0 12:28PM ttys000 0:00.00 grep com.docker.virtualizationThe diagram below provides a very high level and simplified view of the relationship between the Docker components in a Macintosh deployment.
graph TD
E[Container 1]
F[Container 2]
G[Container 3]
H[Container N]
D[Docker Daemon]
C[Docker VM]
B[HyperKit]
A[macOS]
E --> D
F --> D
G --> D
H --> D
D --> C
C --> B
B --> A
classDef host fill:#f9d,stroke:#333,stroke-width:2px;
classDef virtualization fill:#9df,stroke:#333,stroke-width:2px;
classDef vm fill:#fd9,stroke:#333,stroke-width:2px;
classDef engine fill:#d9f,stroke:#333,stroke-width:2px;
classDef container fill:#9fd,stroke:#333,stroke-width:2px;
class A host;
class B virtualization;
class C vm;
class D engine;
class E,F,G,H container;
- Hardened Docker Desktop
- Enhanced Container Isolation
- Dockerfile Reference
- UID and GID Explained
USERinstructionGROUPinstruction