Alpine Linux
Docker
User Management
Base Image
Linux Commands

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:

dockerfile
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

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.

dockerfile
1# System user (typical for services)
2RUN addgroup -S appgroup && adduser -S appuser -G appgroup
3
4# Regular user (for interactive access)
5RUN addgroup -g 1001 appgroup && adduser -D -u 1001 -G appgroup appuser

adduser Flag Reference

Alpine's BusyBox adduser flags differ from Debian's useradd. Here is the full comparison:

FlagAlpine adduserDebian useradd equivalent
-SCreate system user-r or --system
-DNo password prompt(default behavior with --disabled-password)
-G groupPrimary group-g group
-u uidSpecify UID-u uid
-h dirHome directory path-d dir
-s shellLogin shell-s shell
-HDo not create home directory-M or --no-create-home
-g gecosGECOS 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:

dockerfile
1FROM alpine:3.20
2
3RUN apk add --no-cache nodejs npm
4
5# Create non-root user
6RUN addgroup -S appgroup && adduser -S appuser -G appgroup
7
8# Set up application directory
9WORKDIR /app
10COPY --chown=appuser:appgroup package*.json ./
11RUN npm ci --production
12COPY --chown=appuser:appgroup . .
13
14# Switch to non-root user
15USER appuser
16
17EXPOSE 3000
18CMD ["node", "server.js"]

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:

dockerfile
1FROM alpine:3.20
2
3ARG UID=1000
4ARG GID=1000
5
6RUN addgroup -g $GID appgroup && \
7    adduser -D -u $UID -G appgroup -h /home/appuser appuser
8
9WORKDIR /home/appuser/data
10RUN chown appuser:appgroup /home/appuser/data
11
12USER appuser
13CMD ["sh"]

Build with custom IDs:

bash
docker build --build-arg UID=$(id -u) --build-arg GID=$(id -g) -t myapp .

Python Application with Virtual Environment

dockerfile
1FROM python:3.12-alpine
2
3RUN addgroup -S appgroup && adduser -S appuser -G appgroup
4
5WORKDIR /app
6
7COPY requirements.txt .
8RUN pip install --no-cache-dir -r requirements.txt
9
10COPY --chown=appuser:appgroup . .
11
12USER appuser
13CMD ["python", "main.py"]

Multi-Stage Build with User Setup

In multi-stage builds, the user only needs to exist in the final stage:

dockerfile
1# Build stage
2FROM alpine:3.20 AS builder
3RUN apk add --no-cache go
4WORKDIR /build
5COPY . .
6RUN go build -o /app
7
8# Runtime stage
9FROM alpine:3.20
10RUN addgroup -S appgroup && adduser -S appuser -G appgroup
11COPY --from=builder --chown=appuser:appgroup /app /usr/local/bin/app
12USER appuser
13CMD ["app"]

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):

dockerfile
1RUN addgroup -S appgroup && \
2    addgroup -S ssl-cert && \
3    adduser -S appuser -G appgroup && \
4    adduser appuser ssl-cert

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:

dockerfile
RUN addgroup -S appgroup && \
    adduser -S -G appgroup -s /bin/sh appuser

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:

bash
1docker run --rm myapp id appuser
2# uid=100(appuser) gid=101(appgroup) groups=101(appgroup)
3
4docker run --rm myapp cat /etc/passwd | grep appuser
5# appuser:x:100:101:Linux User,,,:/home/appuser:/sbin/nologin

Common Pitfalls

  • Using Debian useradd syntax in Alpine. Alpine does not have useradd unless you install the shadow package. The BusyBox adduser command has different flags, and -D means different things on each platform.
  • Forgetting to use --chown with COPY instructions. Files copied before the USER directive 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 adduser without a corresponding USER directive means the container still runs as root, defeating the purpose.
  • Setting the USER directive 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 shadow package just to use useradd. This adds unnecessary size to an Alpine image. Use the built-in BusyBox adduser and addgroup commands instead.

Summary

  • Alpine uses BusyBox adduser/addgroup, which have different flags than Debian's useradd/groupadd.
  • Use -S for system users (services) and -D for regular users (interactive access).
  • Always pair user creation with a USER directive and --chown on COPY instructions.
  • Specify explicit UID/GID when containers interact with mounted volumes.
  • Place the USER directive after all root-requiring operations like package installation and directory setup.
  • In multi-stage builds, only the final stage needs the user definition.

Course illustration
Course illustration

All Rights Reserved.