Alpine Linux Containers
What is Alpine Linux?
Many official Docker container images come in an alpine
variant.
They are based on Alpine Linux, an incredibly lightweight distribution
that results in smaller image sizes. For example, the official Alpine-based
Golang image is almost 3x smaller than its Debian-based counterpart:
docker pull golang:1.19-bullseye
docker pull golang:1.19-alpine
docker images --filter 'reference=golang' \
--format '{{ .Repository }}:{{ .Tag }}\t{{ .Size }}'
golang:1.19-bullseye 993MB
golang:1.19-alpine 354MB
Alpine Linux achieves these gains by omitting many packages that are useful for a desktop OS, but are less important for a container image. The distribution uses musl libc instead of glibc, and busybox instead of dash or bash. Unfortunately, the barebones environment does result in a steeper learning curve and may cause incompatibilities with some projects.
Working with Alpine Images
Package Management
Alpine packages are installed via apk
, a process that is arguably simpler than
Debian's apt-get
:
FROM golang:1.19-alpine
RUN apk add --no-cache gcc musl-dev
The --no-cache
flag prevents temporary files from being written to the container image
and saves us from needing additional commands to sync the local package cache and clean it
when done.
Sometimes a set of packages is temporarily required as a build dependency, not a runtime dependency.
To uninstall packages, use apk del
:
FROM golang:1.19-alpine
RUN apk add --no-cache gcc musl-dev \
&& go build ./... \
&& apk del gcc musl-dev
Repeating the package list in both the add
and del
commands is cumbersome. Instead, we can rewrite
this operation to use a virtual package:
FROM golang:1.19-alpine
RUN apk add --no-cache --virtual .build-deps gcc musl-dev \
&& go build ./... \
&& apk del .build-deps
Here, we create a new package called .build-deps
, and install gcc
and musl-dev
under it.
Then, deletion can reference .build-deps
instead of the individual names.
Entrypoint
Docker containers should have an init process as the entrypoint. The init process is PID 1 -- the first process in the tree. It must be able to forward signals and clean up zombie processes, responsibilities that are usually not left to ordinary applications.
On Alpine, a good init system to use is tini
:
FROM golang:1.19-alpine
RUN apk add --no-cache tini
ENTRYPOINT ["/sbin/tini", "--"]
Alpine Releases
At any given moment, there are several actively-supported release branches of Alpine Linux.
It is usually best to use the latest version. However, if you do need to pin a specific release,
most official Docker repositories offer those image tags. For example, in the above dockerfiles,
we could have used golang:1.19-alpine3.17
in place of golang:1.19-alpine
.
Musl Libc
The largest obstacle to widespread deployment of Alpine Linux is musl libc. Musl is an alternate implementation of the C standard library, which is not perfectly compatible with glibc. As a result, some programs designed to work with glibc may fail to compile or may crash under musl.
Musl compatibility may not be a concern in simple projects. However, even if your own code
doesn't interact directly with the C standard library, your subdependencies and frameworks might.
For instance, Python packages are distributed as "wheels" that use the manylinux
standard
(PEP 600), designed specifically for glibc. musllinux
wheels exist, but are less widely adopted.
Whenever a compatible wheel cannot be downloaded, Python needs to install a package locally
from source. This process is slower and requires more compile-time dependencies, especially for
components like database drivers.
Similar issues exist in the Node.js ecosystem. Browser drivers and frontend test runners are difficult to deploy in a musl environment due to the use of prebuilt binaries.
The Benefits of Alpine
If you can build and run your project under Alpine Linux, it is worth doing so just to speed up your CI/CD pipeline. Smaller images take less time to upload to a container registry, and less time to download onto your production hosts. The benefits add up quickly as your environment scales up to handle multiple containers at a time.
Aside from efficiency, there is also a security benefit. Since Alpine containers have so few packages preinstalled, the attack surface is much smaller compared to other container distributions. So, the next time you need to dockerize your project, consider moving beyond a default Debian image.