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
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 format
USER <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
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
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!
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.
/etc/passwd entries looks as follows
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.
- name: demo
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.
Some essential takeaways:
- ensure that user and primary group are defined in
- 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. 🚀