Alpine Linux

(January 2017)

Alpine linux is a light-weight distribution. Because it’s so light-weight, Alpine is a good fit for containers.

What makes Alpine distinctive includes applying the grsec/PaX patches to the kernel. It also includes the choice of musl libc, and BusyBox for the core utilities. These choices make Alpine especially minimalist and secure.

Another distinctive part of Alpine is its variety of “installation modes.” It can be installed to a hard disk/SSD/other storage medium like any distro; this is called the sys mode. When working with removable media, consider two other installation modes. In both of these modes, boot the machine from a static ISO image (a CD or a USB partition configured like the CD). A writable medium is also needed to save updates to the system. In the diskless mode, this writable medium is typically a USB key or a second partition. Both the base Alpine system and all persistent changes will be unpacked into a memory-based filesystem. The other, data mode, deals with large amounts of persistent data kept on a persistent storage medium, rather than unpacked into memory. The default setup in data mode stores the /var partition directly on the hard storage medium. But as in the diskless mode, the root system comes from a static ISO image. The second and third modes are sometimes referred to, collectively, as “run-from-RAM” installations.

Key to the second and third modes is Alpine’s lbu utility. This tracks which files differ from their static ISO version and need to persist. The lbu utility saves those changes in .apkovl “overlay” files (essentially tar-gzip archives).

Its package manager is apk, which works somewhat like the FreeBSD ports collection.

Alpine uses OpenRC for init, rather than systemd.

Manual pages are not installed by default. If needed:

# apk add man

And, furthermore, install per-package man pages:

# apk add foo-doc

Set up sshd:

# apk add openssh
# rc-update add sshd
# /etc/init.d/sshd start

Setting the time zone:

# apk add tzdata
# cp /usr/share/zoneinfo/America/Detroit /etc/localtime
# echo 'America/Detroit' > /etc/timezone
# apk del tzdata

Add s6 for service supervision:

# apk add s6
# rc-update add s6-svscan boot

The shell is the Busybox “ash” shell. To set something like my preferred prompt:

# PS1='--- \h \w \$ '

/bin/ash reads ~/.profile for a login shell.

# cat << EOF > /root/.profile

When using lxc-attach, do “su -” to trigger a login shell that runs .profile.

export PS1=‘— \h \w \$ ’ export PAGER=‘less’ alias l=‘ls’ alias ll=‘ls -lah’ alias la=‘ls -a’ EOF


Pull updates:

# apk update

Install all updates:

# apk upgrade

To clean out the package cache:

# apk cache clean

Search packages by name:

# apk search -v 'nginx*'

Search package descriptions:

# apk search -v --description 'NTP'

Add a package:

# apk add nginx

Remove a package:

# apk del nginx

Show installed packages:

# apk info


OpenRC operates on top of sysvinit. /etc/inittab runs /sbin/openrc sysinit, /sbin/openrc boot, and /sbin/openrc default. “sysinit”, “boot”, and “default” are runlevels that correspond to directories in /etc/runlevels/. Each of the runlevel directories contain shell-ish scripts that get evaluated by openrc-run. The scripts in /etc/runlevels/*/ are symlinks to scripts in /etc/init.d/. /etc/init.d/ may also hold scripts that are not activated at a runlevel. Basic “variable setting” local service configuration can be done in /etc/conf.d/*; these files will survive package upgrades and so forth.

OpenRC has a couple of daemons: start-stop-daemon and supervise-daemon. The former assists with starting and stopping services; the latter restarts them if they crash. Apparently, supervise-daemon is still considred “experimental” (as of 2016), so s6 is suggested. Apparently, s6 can also replace start-stop-daemon.

Documentation of OpenRC is lacking. This is the best tour I’ve found:

Show overview of services:

# rc-status

Restart a service:

# rc-service nginx restart

Make a service start at the default runlevel:

# rc-update add nginx default

Show all known services:

# rc-update show -v

Minimal OpenRC init script:


description="fcgiwrap cgi daemon"

command_args="-c 3 -s unix:/var/run/nginx/fcgiwrap.sock"

depend() {
	need net localmount
	after firewall

start() {
	ebegin "Starting ${name}"
	start-stop-daemon --exec ${command} \
	--pidfile ${pidfile} --make-pidfile \
	--background \
	-u ${user} -g ${group} \
	--start -- ${command_args}
	eend $?

The init script must be executable. See the examples in /etc/init.d/. Also openrc-run(8), which is perhaps the best documentation of OpenRC init scripts.

# apk add man openrc-doc

All init scripts are assumed to have the following functions:


There are default implementations in rc/sh/ - this allows very compact init scripts. These functions can be overriden per init script as needed.

Runlevels are directories in /etc/runlevels/. After adding a new runlevel directory, run install -d /etc/runlevels/$runlevel.

s6 and runit

Because Alpine includes Busybox, it may eventually get the Busybox runit applets.

For some reason, OpenRC seems to support s6 as an alternative to supervise-daemon. Furthermore, Alpine packages s6.

To specify s6 as the supervisor for an OpenRC service, place this in its init file:


To use s6, install it and enable it in OpenRC:

# apk add s6
# rc-update add s6-svscan boot

For every service/daemon controlled by s6, an sv-supervise process watches it. s6-svscan is a supervisor of supervirsors — it watches a flock of s6-supervise processes. Configuration of a daemon is handled by its controlling sv-supervise process, based out of a “service directory”. All service directories controlled by s6-svscan are collected under a “scan directory”. The s6-svc command controls a sv-supervise process, and lets us signal its daemon. The s6-svcscanctl command controls a set of sv-supervise processes, and start or stop entire process trees.

See for more about s6.

s6 sets the scan directory as the argument to s6-svscan or (if not supplied) its current directory. On Alpine, the /etc/init.d/s6-svscan script sets the scan directory to /run/openrc/s6-scan (a tmpfs). How is that supposed to work? Because, under OpenRC with s6 supervision, each service supervised by s6 has an OpenRC init script too. That OpenRC init script can specify s6_service_path, which OpenRC symlins into /run/openrc/s6-scan. By default, OpenRC sets s6_service_path to /var/svc.d/${RC_SVCNAME}.

So, an OpenRC service file for a daemon supervised by s6 might look like:

description="myservice does my stuff"
depend() {
	need s6-svscan net localmount
	after firewall
} > If you write your own start, stop and status functions in your service script, none of this will work. > You must allow OpenRC to use the default functions.

We also need the s6 service directory, of course. See for more about s6.