How do I add a user when I'm using Alpine as a base image?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
To add a user in an Alpine-based Docker image, use the adduser command with the -D flag (no password) or the -S flag (system user). Alpine uses BusyBox versions of adduser and addgroup, which have different flags than the Debian/Ubuntu useradd command. Getting this wrong is one of the most common Dockerfile mistakes when switching from Debian-based images to Alpine.
The quick answer for most use cases:
This creates a system group and a system user. The rest of this guide covers when to use system users vs. regular users, how to set up home directories and shells, and how to handle the differences between Alpine's BusyBox adduser and Debian's useradd.
System User vs. Regular User
Alpine's adduser supports two modes, and choosing the right one matters for container security:
System user (-S): No password, no aging, UID from the system range (typically below 1000). Appropriate for running application processes that should never have interactive login access.
Regular user (-D): Creates a normal user with a home directory and login shell, but without a password prompt (-D means "don't assign a password"). Appropriate when a human might need to docker exec into the container for debugging.
adduser Flag Reference
Alpine's BusyBox adduser flags differ from Debian's useradd. Here is the full comparison:
| Flag | Alpine adduser | Debian useradd equivalent |
-S | Create system user | -r or --system |
-D | No password prompt | (default behavior with --disabled-password) |
-G group | Primary group | -g group |
-u uid | Specify UID | -u uid |
-h dir | Home directory path | -d dir |
-s shell | Login shell | -s shell |
-H | Do not create home directory | -M or --no-create-home |
-g gecos | GECOS field (full name) | -c comment |
Notice that -D in Alpine means "disabled password", while in Debian's useradd, -D means "print defaults". This is a frequent source of confusion when porting Dockerfiles between base images.
Complete Dockerfile Examples
Minimal Service Container
For a typical application container where the process should run as a non-root user:
Specific UID/GID for Volume Mounts
When your container writes to mounted volumes, the UID inside the container must match the host UID to avoid permission issues:
Build with custom IDs:
Python Application with Virtual Environment
Multi-Stage Build with User Setup
In multi-stage builds, the user only needs to exist in the final stage:
Adding a User to Multiple Groups
If the user needs access to resources owned by different groups (for example, a process that reads TLS certificates owned by a ssl-cert group):
The first adduser call creates the user with a primary group. The second adduser call (without -S) adds an existing user to a supplementary group.
Setting a Custom Shell
By default, system users created with -S get /sbin/nologin as their shell. If you need shell access for debugging:
For production containers, keeping /sbin/nologin is a security best practice since it prevents interactive login even if someone gains access to the container.
Verifying User Configuration
After building, you can inspect the user setup:
Common Pitfalls
- Using Debian
useraddsyntax in Alpine. Alpine does not haveuseraddunless you install theshadowpackage. The BusyBoxaddusercommand has different flags, and-Dmeans different things on each platform. - Forgetting to use
--chownwithCOPYinstructions. Files copied before theUSERdirective are owned by root, and the application user will not be able to read or write them. - Creating the user but never switching to it. Adding
adduserwithout a correspondingUSERdirective means the container still runs as root, defeating the purpose. - Setting the
USERdirective too early. Package installation (apk add), directory creation, and permission changes typically require root. Switch to the non-root user as late as possible in the Dockerfile. - Not specifying UID/GID when containers write to mounted volumes. Without explicit IDs, Alpine assigns the next available UID, which may not match the host user, causing permission denied errors.
- Installing the
shadowpackage just to useuseradd. This adds unnecessary size to an Alpine image. Use the built-in BusyBoxadduserandaddgroupcommands instead.
Summary
- Alpine uses BusyBox
adduser/addgroup, which have different flags than Debian'suseradd/groupadd. - Use
-Sfor system users (services) and-Dfor regular users (interactive access). - Always pair user creation with a
USERdirective and--chownonCOPYinstructions. - Specify explicit UID/GID when containers interact with mounted volumes.
- Place the
USERdirective after all root-requiring operations like package installation and directory setup. - In multi-stage builds, only the final stage needs the user definition.

