Alpine Linux: apk, BusyBox, musl and the Docker image

Contents
What Alpine Linux is
Alpine next to Debian, Ubuntu and Rocky
Which Alpine is this
Packages: apk
Alpine in a Dockerfile
BusyBox: where commands differ
No bash: the script that is not found
musl: glibc programs do not run
Users and groups
Time zones
Checklist: moving to Alpine

What Alpine Linux is

Alpine Linux is a small Linux distribution built on three parts that differ from Debian, Ubuntu and Rocky Linux: the musl C library instead of glibc, BusyBox instead of the GNU tools, and its own package manager, apk. Its Docker image is 8.43 MB, which makes it a common base for containers; the official OpenCode image is one of them (OpenCode in Docker).

On a server or a virtual machine Alpine boots with OpenRC. In a container no init system runs, and this page is about containers.

Every command below was tested on 2026-10-26 in Docker 29.1.3 with alpine:latest, which was Alpine 3.24.3.

Alpine next to Debian, Ubuntu and Rocky

The same checks in four official images: alpine:latest, debian:13, ubuntu:24.04 and rockylinux/rockylinux:9.

Alpine 3.24Debian 13Ubuntu 24.04Rocky 9.8
Image size8.43 MB120 MB78.2 MB236 MB
C librarymuslglibc 2.41glibc 2.39glibc 2.34
/bin/shBusyBox ashdashdashbash
bashnot installedyesyesyes
ls, grep, find, dateBusyBoxGNU 9.7GNU 9.4GNU 8.32
Packagesapkapt-getapt-getdnf
New useradduseruseradduseradduseradd, adduser
Download toolwgetnonenonecurl

Image size as docker image ls shows it; GNU means GNU coreutils, and the wget of Alpine is part of BusyBox.

Each row is a habit that breaks when a script or a Dockerfile moves from one of the others to Alpine. The chapters below go through them.

Which Alpine is this

cat /etc/alpine-release

3.24.3

The file every distribution has gives the same version and the name:

cat /etc/os-release

NAME="Alpine Linux" ID=alpine VERSION_ID=3.24.3 PRETTY_NAME="Alpine Linux v3.24" HOME_URL="https://alpinelinux.org/" BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues"

Packages: apk

CommandWhat it doesIn the test
apk updatedownloads the package indexes to /var/cache/apktwo files, 2.9 MB
apk add --no-cache bashinstalls a package; fetches the indexes for this one run and keeps nothingthe cache stayed at 4.0 KB
apk search -q 'php8*'finds packages by namephp83, php84, php85 among others
apk info -e bashprints the name when the package is installedbash
apk del bashremoves a packageback to 16 packages
apk list --installedlists the installed packages16 in the plain image

Versions in names. Some packages carry their version in the name, so the PHP packages php83, php84 and php85 exist side by side. The command carries it too: php83 installs /usr/bin/php83 and no php, so a Dockerfile adds the link itself (next chapter).

Virtual packages. Build tools for one step, removed with one name afterwards:

apk add --no-cache --virtual .build-deps gcc musl-dev

apk del .build-deps

In the test the first command grew the system from 16 to 31 packages and 197.9 MiB; the second brought it back to 16 packages and 8208 KiB. In a Dockerfile both belong in one RUN: a later layer that deletes files does not make the earlier one smaller.

Alpine in a Dockerfile

vi Dockerfile

FROM alpine:3.24 RUN apk add --no-cache php83 php83-ctype php83-mbstring \ && ln -s /usr/bin/php83 /usr/local/bin/php RUN addgroup -g 1000 app && adduser -D -u 1000 -G app app USER app

The image built in the test ran php -v as PHP 8.3.36 (cli) under UID 1000, and docker image ls showed 21.4 MB. More on images: Dockerfile.

BusyBox: where commands differ

ls, grep, find, date, sed, ps and wget are one program on Alpine, BusyBox, and it knows fewer options than the GNU tools. The same commands in debian:13 and alpine:latest:

CommandDebian 13 (GNU)Alpine (BusyBox)
echo a1 | grep -P "\d"a1grep: unrecognized option: P, exit code 2
date -d "+1 day" +%Fthe date of tomorrowdate: invalid date '+1 day'
date -d "2026-10-26 12:00" +%AMondayMonday
find /etc -maxdepth 0 -printf "%f\n"etcfind: unrecognized: -printf
ls --versionls (GNU coreutils) 9.7ls: unrecognized option: version
sed -i "s/bash/sh/" hedits the fileedits the file

A missing option prints the usage of the BusyBox applet, so the error can scroll off the screen. The GNU tools are packages:

apk add --no-cache grep coreutils findutils

After it the three failing commands printed what Debian printed. The other way is to keep scripts to options both have: grep -E with [0-9] instead of grep -P with \d.

No bash: the script that is not found

A script that starts with #!/bin/bash fails on Alpine with a message that points at the wrong file:

./hello.sh

sh: ./hello.sh: not found

The script is there, and the exit code is 127. What is not found is /bin/bash from its first line. Two ways out:

musl: glibc programs do not run

Programs built on Debian, Ubuntu or Rocky Linux use glibc. On Alpine they fail with the same misleading message. Here /usr/bin/true, copied from debian:13:

./deb-true

sh: ./deb-true: not found

ldd shows why: the program asks for the glibc loader and for functions that musl does not have:

ldd ./deb-true

/lib64/ld-linux-x86-64.so.2 (0x733bac9c3000) libc.so.6 => /lib64/ld-linux-x86-64.so.2 (0x733bac9c3000) Error relocating ./deb-true: __vfprintf_chk: symbol not found Error relocating ./deb-true: __printf_chk: symbol not found Error relocating ./deb-true: __fprintf_chk: symbol not found

gcompat is a compatibility layer for such programs. With it, this small one ran with exit code 0:

apk add --no-cache gcompat

It is a layer, not glibc. For a real program, install its Alpine package or build it on Alpine.

Users and groups

useradd is not there: sh: useradd: not found. BusyBox has addgroup and adduser:

addgroup -g 1000 agent

adduser -D -u 1000 -G agent agent

id agent

uid=1000(agent) gid=1000(agent) groups=1000(agent),1000(agent)

-D creates the user without a password, so the command does not stop to ask for one; in a Dockerfile it has to. The home folder is /home/agent and the shell /bin/sh. More on users in general: Linux users.

Time zones

The image has no time zone data, and TZ is ignored without an error:

TZ=Europe/Helsinki date +%Z

UTC

apk add --no-cache tzdata

TZ=Europe/Helsinki date +%Z

EET

Checklist: moving to Alpine

Article author: Klavdiya Kondakova

Related articles
Linux
Alpine Linux: apk, BusyBox, musl and the Docker image
Debian
Ubuntu
Rocky Linux
Docker
Dockerfile
OpenCode in Docker: auto mode on container/src only
Banner image

Search this site

Channel @aofeed Chat @aofeedchat

Contacts and cooperation:
I recommend our hosting beget.com
Write to info@aredel.com if you:
1. Want to write an article for our site or translate an article into your native language.
2. Want to place thematically relevant ads on the site.
3. Ads on my site pass maximum censorship. If you see an ad block unsuitable for school-age children, shocking or misleading - please contact us by e-mail
4. Found a mistake, inaccuracy, bug, etc. on the site. ... .......
5. Articles can be shared on social media by clicking a network icon: