Mastering Dockerfile USER: The Key to seamless Kubernetes Deployment
--
Linux users and their corresponding file permissions are essential Linux security features. The containers are nothing more than processes contained by Linux namespaces running with permissions inherited from a user. Understanding these concepts allow easily to extend them to higher level abstractions like Dockerfiles, Kubernetes cluster, container runtime, etc.
So, here’s the deal: Linux sees everything as a file, even users and groups! We’re about to create a new “app” user (UID 1000), being a member of the primary group “app” (GID 2000).
groupadd --gid 2000 app
useradd -u 1000 -g app app
The first command adds the app:x:2000
to the/etc/groups
file. The second one adds app:x:1000:2000::/home/app:/bin/sh
string to the /etc/passwd
file.
Why is all this important, you ask? Well, these files are like secret ingredients in your grandma’s famous recipe. Many programs count on their existence, and when they’re missing, chaos reigns, and nobody knows why.
The Dockerfile USER drama
A USER in a Dockerfile is an instruction that allows to define the default user that container runtime is about to use. It has the following formatUSER <user>:[group]
or USER <uid>:[gid].
It is worth noting that group name (or group ID) is optional. If a Dockerfile specifies only user (USER app), then you must make sure that Linux user have a primary group defined in /etc/passwd
file. Otherwise the default group falls back to the root!
USER in a multi-stage build
Now, we dive into the world of multi-stage Dockerfile, because this how we do it these days.
FROM golang:1.21-bullseye as builder
RUN apt update && apt -y install apt-transport-https ca-certificates
RUN groupadd --gid 2000 app && useradd -u 1000 -g app app
FROM scratch
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group
COPY --chown=app:app --from=builder /usr/src/app/webhook /app/webhook
USER app
EXPOSE 443
ENTRYPOINT ["/app/webhook"]
The first stage (builder) is using rich in many utilities based image. At this stage binaries are build and user with group created. The second stage (FROM scratch). It’s like teleporting to a minimalist realm. Here, we simply copy the goodies (binaries, /etc/passwd, and /etc/group files) from our previous stage. Don’t forget to use --chown=app:app
when copying to ensure that our user has the keys to the kingdom!
User nobody
The nobody user is set in almost every Linux distribution with UID and GID equal to 65534. Since the container namespaces are isolated by default it is considered as a best practices to execute on behalf of this user your containerised application. If opt in using the nobody user you do not have to execute the groupadd and the useradd commands.
The /etc/groups
and /etc/passwd
entries looks as follows
nogroup:x:65534:
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
Kubernetes and Pod security context
Kubernetes, our magical orchestrator in the land of distributed computing, comes into play, like a director in charge of a circus of containers.
Kubernetes introduces the security guards at the container party. The securityContext with runAsUser can override what you defined in the Dockerfile. However the mistmatch between the Kubernetes `runAsUser` and Dockerfile USER may cause file permission issues or ambiguous system errors mentioned earlier. Therefore it is the best to define the default non-root USER in Dockerfile and set it in the security context to ensure smooth runtime in Kubernetes. The below example is instructing Kubernetes to run container as the app user and group.
apiVersion: v1
kind: Pod
metadata:
name: demo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 2000
containers:
- name: demo
image: <yourimage>
securityContext:
allowPrivilegeEscalation: false
At the continer section the allowPrivilegeEscalation is set to false. This falg ensures that low level system calls will always perform with the no_new_priv flag set. Having that, the container cannot gain more linux capabilities during the container runtime.
Summary
Some essential takeaways:
- ensure that user and primary group are defined in
/etc/passwd
and/etc/groups
files, - use Pod and Container securityContext to enforce running containers as not root user and group,
So, there you have it — Dockerfile, users and Kubernetes. Enjoy your journey through the Linux funhouse, and may your containers be as harmonious as a jazz ensemble on a moonlit night. 🚀