TIL: User and group management under BusyBox
Note: This post is part of my TIL (“today I learned”) series, where I describe (usually) short problems and their solutions I encountered that day. Find all posts of this series here.
TLDR
BusyBox’s version of adduser
and addgroup
can be used to add users to groups (usually done via usermod
).
# this common usermod invocation to add $GROUP to $USERs groups
# is equivalent to the following BusyBox adduser call
# or even this
How I got there
In a recent effort to build a custom nginx
docker image that includes the awesome ngx_http_geoip2 module, I took a closer look at the BusyBox man pages and more specifically its adduser
and addgroup
“applets” (as they call their commands).
A common task my custom docker images solve for me, is the creation of specific user and/or groups that either differ from or straight up don’t exist in the upstream images. In the case of the nginx
image, I wanted run it under a www-data
user that matches my host system (compared to the nginx
user and group with UID/GID 101 that the image uses).
In past images, I had often used usermod
which is available on Alpine via the shadow
package to add existing users to a newly created group. However, I had always wondered if one really needed to install a package to “just modify a user’s groups” after initial creation.
For some reason, the addition of a new image to my collection (which is a rare task compared to the regular version bumps for new Alpine releases), prompted me to investigate if shadow
was really needed. I cannot quite recall, where I got the initial solution from 1, but among BusyBox’ many commands there was bound to be one offering this functionality. And sure enough, addgroup
and adduser
(among the first three commands(!)) both offer a way to emulate the desired behavior, as the following snipped shows:
# this common usermod invocation to add $GROUP to $USERs groups
# is equivalent to the following BusyBox adduser call
# or even this
While I was both amazed and bummed out2 by my discovery at the same time, I quickly started to update the latest tags of my existing images producing the following, embarassingly short diff (see also the full commit on GitHub):
diff --git a/rspamd/alpine-3.21/Dockerfile b/rspamd/alpine-3.21/Dockerfile
index b0f46be..343616b 100644
ARG VMAIL_GID="5000"
-RUN apk add --no-cache shadow \
+RUN apk add --no-cache \
rspamd \
rspamd-controller \
rspamd-proxy \
ca-certificates \
&& rm -rf /var/cache/apk/*
-RUN addgroup --gid ${VMAIL_GID} vmail \
- && usermod -aG vmail rspamd
+RUN addgroup -g ${VMAIL_GID} vmail \
+ && adduser rspamd vmail
RUN mkdir /run/rspamd \
&& chown rspamd:vmail /run/rspamd \
It’s simple, yet beautiful: I opted for the addgroup variant in this image to match the previous usage and the diff could have been even shorter, had I not also shortened the --gid
argument in the same commit.
Naturally, I wanted to know how big the impact of this simple change would be on the sizes of my images3. Using dive (big shoutout, its visualization of image layers, helped me immensely when initially familiarizing myself with containers), I saw a decrease from 80mb to 78mb for the rspamd
container also shown in the diff above.
Not bad for a simple (almost) one-line change with no downsides!
Probably an instance of the XY problem of me searching for usermod
under Alpine/BusyBox rather than looking for a solution of how to add a user to a group
For not having invested the 15 minutes to find the proper solution years ago
Which, as we all know, is the most important metric for any docker image..