The default user in the Docker image
This fifth article concludes the series of Docker best practices that deserve more love. We will look closely at the default user in the image and pave the way for uninterrupted usage of Docker platform. Make sure you reviewed the previous articles:
The default user in the Docker image
Regardless of the operating system, it is always good practice to reasonably use administrator privileges. Whether it's the root
user on Unix-like systems or the disabled UAC
module on Windows the effect may be the same and lead to increased chances of malicious code breaking through security. The principles are the same when it comes to Docker images, despite the fact that it introduces additional isolation between the host system and containers.
So what is the default user? The answer is: the one that was set in the base image or root
if the user remained unchanged. Using root
gives us some advantages, such as the possibility to install additional dependencies. Due to the location of some libraries, these operations cannot be performed with limited privileges.
Ultimately, we aim to change the user that will be least privileged, which in Dockerfile is quite simple. This is done with the USER
instruction which comes in two variants:
USER <user>[:<group>]
USER <UID>[:<GID>]
The first variant requires the textual names of the user and group we want to switch to, while the latter allows us to use their numeric identifiers directly. Let’s find out how the build process works when USER
instruction in both variants is used.
Textual user name as nginx
:
tips@u11d:~$ echo " > FROM alpine:3.16 > USER nginx > " > Dockerfile tips@u11d:~$ sudo docker build . Sending build context to Docker daemon 10.75kB Step 1/2 : FROM alpine:3.16 ---> 9b18e9b68314 Step 2/2 : USER nginx ---> Running in b67f70601e4c Removing intermediate container b67f70601e4c ---> 8410027170c6 Successfully built 8410027170c6 tips@u11d:~$ sudo docker run --rm -it 8410027170c6 docker: Error response from daemon: unable to find user nginx: no matching entries in passwd file.
Numeric user identifier as 1000
:
tips@u11d:~$ echo " > FROM alpine:3.16 > USER 1000 > " > Dockerfile tips@u11d:~$ sudo docker build . Sending build context to Docker daemon 9.728kB Step 1/2 : FROM alpine:3.16 ---> 9b18e9b68314 Step 2/2 : USER 1000 ---> Running in 869f2e785e83 Removing intermediate container 869f2e785e83 ---> 7d0dcceafd9f Successfully built 7d0dcceafd9f tips@u11d:~$ sudo docker run --rm -it 7d0dcceafd9f $ id uid=1000 gid=0(root)
As we can see from the examples above, both versions were built without a problem. Unfortunately, only the version with a numeric user ID was successfully executed. This is because the USER
instruction does not create a user in the image, but only switches to it. Docker, while running the container, could not replace the user's name with his UID (numeric ID), which caused the error.
Need support in Docker platform?
Whether it's integration, containerization or modernization, uninterrupted can handle it efficiently and seamlessly.
The solution is to manually add the user before using it. In images based on the Alpine distribution, we can do this with the adduser -D -u 1000 username
command, where:
-D
parameter disables the password configuration,-u 1000
sets the numeric user and group ID to the indicated value,username
is the name of the new user.
tips@u11d:~$ echo " > FROM alpine:3.16 > RUN adduser -D -u 1000 nginx > USER nginx > " > Dockerfile tips@u11d:~$ sudo docker build . Sending build context to Docker daemon 9.728kB Step 1/3 : FROM alpine:3.16 ---> 9b18e9b68314 Step 2/3 : RUN adduser -D -u 1000 nginx ---> Running in a788a92773f9 Removing intermediate container a788a92773f9 ---> 84aecbe49298 Step 3/3 : USER nginx ---> Running in 4db5f569c0dc Removing intermediate container 4db5f569c0dc ---> 63a098041085 Successfully built 63a098041085 tips@u11d:~$ sudo docker run --rm -it 63a098041085 $ id uid=1000(nginx) gid=1000(nginx)
As a result the user is successfully created and changed in the image. The next step is to adjust file and directory permissions. When copying data to the image with COPY
or ADD
instructions the ownership still remains root
, which is a default behavior. Docker's authors anticipated this situation and added the ability to change ownership on the fly. This saves the extra RUN
instruction, which would have to do it separately.
To change the ownership of copied files and directories use the --chown=<user>:<group>
parameter of the COPY
or ADD
instruction.
FROM alpine:3.16 RUN adduser -D -u 1000 nginx COPY . .
If you have trouble remembering the chown
shortcut you can always think of its full version - "change owner".
Conclusion
Summarizing, the security principle of least privilege (POLP) can be applied to the Docker platform and containerization concept. It is always a good practice to prevent uncontrolled files or directories ownership and give only the minimum level of access required. On the other hand it is a modus operandi that ownership as well as the process is executed by the same system user. This is definitely a good design as well as a way to meet the security rules and policies and improve the overall security of the system.
Still feeling unsure or need more information about the Docker platform? You can always go through our Docker series to catch up with things.
If this is not enough and you need professional support, do not hesitate to contact us. We are always happy to help.