Alpine Linux: tiny Linux for containers
Alpine is the Linux you've probably already run without knowing it. It's inside a huge number of containers, because its whole base system is about 8 MB (Ubuntu's container image is about 80). It also runs on routers, Raspberry Pis and small cloud servers. Alpine is small because it does almost everything its own way, so let's meet it properly, the way most people do: inside a container.
You will learn
- Running an Alpine container with
podman(anddocker) apk, Alpine's package manager- What makes Alpine different: BusyBox, the ash shell, musl, OpenRC, and no sudo
- When Alpine is a great choice, and when it isn't
Why so small?
- BusyBox replaces about 300 separate programs (
ls,grep,sed,ping…) with one ~800 KB file. - musl replaces glibc, the standard C library every Rocky and Ubuntu program is built on. musl is smaller and simpler.
- Nothing extra is installed. No bash, no sudo, no man pages, no curl. You add exactly what you need with
apk.
Fewer programs also means fewer security bugs to patch, which is one reason people build containers on it. A new Alpine release comes out every six months (May and November), and each one gets about two years of updates.
Run Alpine in a container
A container runs a program with its own private set of files (here, a whole mini Alpine system), while sharing the host computer's kernel. It starts in a second, and you can throw it away when you're done. podman runs containers as your normal user, with no root needed. Docker does the same job and uses the same commands, but its background service runs as root.
sudo dnf install podman podman run -it docker.io/library/alpine:3.22
podman is Red Hat's tool. Docker isn't in Rocky's repos: install podman-docker if you want a “docker” command that runs podman.
sudo apt install podman
podman run -it docker.io/library/alpine:3.22
# or Docker:
sudo apt install docker.io
sudo docker run -it alpine:3.22
Docker needs sudo (or the docker group, which is as powerful as root).
-it means “interactive, with a terminal”, and alpine:3.22 is the image name and version (tag). The full name docker.io/library/alpine says exactly which registry to download from, and it works everywhere. The prompt changes to / #: you're root, in /, inside Alpine. Type exit to leave.
/ # cat /etc/alpine-release
3.22.1
/ # uname -r
5.14.0-570.22.1.el9_6.x86_64 ← the HOST's kernel: containers share it
apk: Alpine's package manager
apk update # refresh the package list apk add bash curl # install apk del bash # remove apk search nginx # find packages apk info # what's installed apk upgrade # update everything cat /etc/apk/repositories # main + community
(1/4) Installing ncurses-terminfo-base (6.5_p20250503-r0) (2/4) Installing libncursesw (6.5_p20250503-r0) (3/4) Installing readline (8.2.13-r1) (4/4) Installing bash (5.2.37-r0) OK: 9 MiB in 19 packages
In container recipes (Dockerfiles) you'll see apk add --no-cache PKG. It installs without keeping the downloaded package list, so the image stays tiny.
How Alpine is different
| Alpine | Rocky | Ubuntu | |
|---|---|---|---|
| Install software | apk add | dnf install | apt install |
| Everyday tools | BusyBox (fewer options) | GNU coreutils | GNU coreutils |
| Shell | ash (/bin/sh); bash is optional | bash | bash (sh = dash) |
| C library | musl | glibc | glibc |
| Services | OpenRC: rc-service, rc-update | systemd | systemd |
| Become root | su, or add doas / sudo | sudo | sudo |
| Add a user | adduser -D alice (BusyBox) | useradd | adduser |
| Logs | /var/log/messages | journalctl | journalctl |
BusyBox: one program, many names
ls -l /bin/ls shows /bin/ls -> /bin/busybox. Almost every command is a link to the same program. BusyBox versions are smaller and have fewer options than the GNU ones, so a script that uses grep -P or date -d "next friday" may fail. apk add coreutils grep installs the full GNU versions if you need them.
ash, not bash
Alpine's /bin/sh is BusyBox ash. It's like Ubuntu's dash (lesson 12) but a bit friendlier: it understands [[ ]] and source. It doesn't do <( ), arrays or {1..5}. That's why the lesson 14 advice pays off: a POSIX #!/bin/sh script runs on Alpine unchanged. Need bash? apk add bash, then bash, and the prompt becomes bash-5.2#.
musl: the “it's right there, but not found” error
A program downloaded for “Linux” is usually built for glibc. On Alpine it can fail with a baffling message:
/ # ./mytool
sh: ./mytool: not found ← but ls shows it's right there!
It isn't mytool that's missing. It's the glibc loader it needs (/lib64/ld-linux-x86-64.so.2). The fixes: use the program's Alpine or musl build, install gcompat (a compatibility layer that sometimes works), or pick a glibc-based image such as debian or almalinux for that app. ldd --version tells you which C library you have.
OpenRC, and why containers don't use it
apk add nginx rc-service nginx start # like: systemctl start nginx rc-update add nginx default # like: systemctl enable nginx rc-status # like: systemctl list-units
Inside a container there is no init system at all. The container runs one program (here, the shell), and when it exits, the container stops. Run rc-service in a container and OpenRC warns you that it “did not boot” this system. Container tools start the app directly instead.
Containers come and go
podman ps -a # all containers, including stopped ones podman images # downloaded images (the templates) podman run --rm docker.io/library/alpine:3.22 cat /etc/alpine-release # one command, then delete it podman rm NAME # delete a stopped container
An image is the template and a container is one running copy of it. Changes you make inside a container (like apk add bash) stay in that container until you remove it. A fresh podman run starts clean from the image again. Real projects write the setup steps into a recipe file, called a Containerfile or Dockerfile, so every container starts the same way. A future Containers path will cover that.
Should I use Alpine?
- Great for: small containers for Go, static or simple apps, routers and appliances, a Raspberry Pi that runs one job, and learning how a Linux system fits together from the pieces up.
- Think twice for: apps that need glibc, big Python/Java/Node stacks with native modules, or when your team only knows Rocky and Ubuntu. Many people pick
debian:13-slimoralmalinux:9-minimalinstead: a bit bigger, but fewer surprises.
Try it: a trip into Alpine 🏔️
You'll start on the Rocky or Ubuntu server, launch an Alpine container, look around, install bash, and come back.
Quick check
1. Inside an Alpine container on a Rocky server, uname -r shows 5.14.0-…el9_6. Why?
✓ That's what makes containers so light compared to virtual machines, which boot their own kernel.
2. bash script.sh in an Alpine container says sh: bash: not found. The quickest fix?
✓ Alpine uses apk, and it doesn't install bash by default.
3. A program you downloaded is definitely in the folder, but Alpine says ./mytool: not found. What's likely going on?
✓ The “not found” is about the missing glibc loader, not the file itself.
4. What's the Alpine equivalent of sudo systemctl enable --now nginx on a real (non-container) Alpine server?
✓ OpenRC: rc-update is “enable at boot”, and rc-service starts or stops it now.
Next up: Git · Track & share your work, starting with “Git: save points for your work”.