Barebone Alpine Containers
Today I can across Amazon’s documentation on their barebones containers,
which uses dnf (Red Hat’s successor to yum) for their Amazon Linux
distribution and so I was inspired to try it with Alpine Linux and its package
manager apk.
For a several months now I have had the idea of exploring the idea of creating a container image from layers where each layer was created from a distributions package. The idea there would be each package being its own layer so you can share layers for the same package between images for different programs for example a layer with libzstd. That isn’t however what is being looked at today.
Alpine Base
The starting point was reading Bootstrapping Alpine Linux, which shows
a statically linked version of apk being used to install the base system.
Typically, the base images for Alpine Linux containers are built from a tarball
containing the root filesystem. That is to say, they don’t use this approach.
FROM alpine:3.21 AS base
ARG ARCH="x86_64"
ARG PACKAGE="alpine-base"
ADD --chmod=0744 https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v2.14.6/x86_64/apk.static /apk.static
# The --allow-untrusted accounts for UNTRUSTED signature.
RUN /apk.static --arch ${ARCH} -X https://dl-cdn.alpinelinux.org/alpine/latest-stable/main/ --root /rootfs --initdb --no-cache --allow-untrusted add ${PACKAGE}
FROM scratch
COPY --from=base /rootfs /
ENTRYPOINT ["/bin/sh"]
There doesn’t seem to be an easy way to avoid the --allow-untrusted as you
can’t provide an explicit signature on the command line. As of this post, the
package depends on 10 other packages so potential of them would be required.
For containers, the alpine-base is perhaps a little more than you would need
as it brings in openrc. So if you were interested in creating a base container
you may be better off with this packages or at least checking what alpine-base
depends on and filtering out what isn’t needed yourself.
alpine-baselayout alpine-conf alpine-release apk-tools busbox busybox-suid musl-utils
The alpine-conf is responsible for proving the setup scripts for installing
and configuring Alpine and extra packages such as sshd so is a candidate for
ignoring.
Single Program
The program chosen to try out this experiment to see if the alpine-base package
can be avoided was curl.
The Containerfile for this is essentially same above but PACKAGE would be curl
and the ENTRYPOINT would be /usr/bin/curl. It is not easy to configure that at
build time as you can’t provide build arguments there as they aren’t substituted
at build time.
Building
podman build --build-arg PACKAGE=curl -t myalpine:curl
Running
podman run --rm -it localhost/myalpine:curl https://google.com
The output as the non-www version was given such that it is smaller is:
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="https://www.google.com/">here</A>.
</BODY></HTML>
Checking
podman save --format oci-archive --output curl_container.tar localhost/myalpine:curl
The complete file list for layer with curl was as follows:
dev/
etc/
etc/apk/
etc/apk/arch
etc/apk/world
etc/ssl/
etc/ssl/cert.pem
etc/ssl/certs/
etc/ssl/certs/ca-certificates.crt
etc/ssl/ct_log_list.cnf
etc/ssl/ct_log_list.cnf.dist
etc/ssl/openssl.cnf
etc/ssl/openssl.cnf.dist
etc/ssl/private/
etc/ssl1.1/
etc/ssl1.1/cert.pem
etc/ssl1.1/certs
lib/
lib/apk/
lib/apk/db/
lib/apk/db/installed
lib/apk/db/lock
lib/apk/db/scripts.tar
lib/apk/db/triggers
lib/ld-musl-x86_64.so.1
lib/libc.musl-x86_64.so.1
proc/
tmp/
usr/
usr/bin/
usr/bin/curl
usr/lib/
usr/lib/engines-3/
usr/lib/engines-3/afalg.so
usr/lib/engines-3/capi.so
usr/lib/engines-3/loader_attic.so
usr/lib/engines-3/padlock.so
usr/lib/libbrotlicommon.so.1
usr/lib/libbrotlicommon.so.1.1.0
usr/lib/libbrotlidec.so.1
usr/lib/libbrotlidec.so.1.1.0
usr/lib/libbrotlienc.so.1
usr/lib/libbrotlienc.so.1.1.0
usr/lib/libcares.so.2
usr/lib/libcares.so.2.19.4
usr/lib/libcrypto.so.3
usr/lib/libcurl.so.4
usr/lib/libcurl.so.4.8.0
usr/lib/libidn2.so.0
usr/lib/libidn2.so.0.4.0
usr/lib/libnghttp2.so.14
usr/lib/libnghttp2.so.14.28.3
usr/lib/libpsl.so.5
usr/lib/libpsl.so.5.3.5
usr/lib/libssl.so.3
usr/lib/libunistring.so.5
usr/lib/libunistring.so.5.1.0
usr/lib/libz.so.1
usr/lib/libz.so.1.3.1
usr/lib/libzstd.so.1
usr/lib/libzstd.so.1.5.6
usr/lib/ossl-modules/
usr/lib/ossl-modules/legacy.so
var/
var/cache/
var/cache/apk/
var/cache/misc/
Bigger System - gdal
Here is an example with the GDAL tools, granted this one may be better if
it didn’t set an entry-point such that when you run it you had to provide the
name of the tool as otherwise you have to overwrite it at runtime if you want
to use something other than gdal_translate.
FROM alpine:3.21 AS base
ARG ARCH="x86_64"
ADD --chmod=0744 https://gitlab.alpinelinux.org/api/v4/projects/5/packages/generic/v2.14.6/x86_64/apk.static /apk.static
# The --allow-untrusted accounts for UNTRUSTED signature,
RUN /apk.static --arch ${ARCH} -X https://dl-cdn.alpinelinux.org/alpine/edge/main/ -X https://dl-cdn.alpinelinux.org/alpine/edge/community/ --root /rootfs --initdb --no-cache --allow-untrusted add gdal-tools
FROM scratch
COPY --from=base /rootfs /
ENTRYPOINT ["/usr/bin/gdal_translate"]