Building / consuming alpine Linux packages inside containers and images

How to build alpine Linux packages you can later install inside other alpine based containers or images
What we will build ?
The best way to build software under alpine Linux is to write an
APKBUILD file and rely on the effective
distribution tools to create a package for
us.
We will create in this tutorial a package inside an alpine container and see how we can consume it inside another container, and for building an image. We will use for that :
the alpine Linux official image and official packages,
and a manifest we will create from scratch to build a package registry service programmed in rust: reposerve
Build environment
We start by creating a tmp directory in the current working directory
mkdir tmpLet’s run an alpine container in interactive mode to build our package. The tmp host directory is
mapped to the packager user home directory.
podman run --rm --interactive --tty \
--volume "$(pwd)/tmp:/home/packager" \
alpine:3.17 shInside the container lets prepare the abuild environment
# install essentials apk package
apk add sudo build-base alpine-sdk
# create a packager user and add him to sudo list
adduser -D packager
addgroup packager abuild
echo 'packager ALL=(ALL) NOPASSWD:ALL' \
>/etc/sudoers.d/packager
# then open an sh as packager user
sudo -u packager shWe will now generate a pair of keys. Follow this step only for the first package as we can reuse the same keys for all other packages we need to build.
abuild-keygen -n --append --installCreate the package
We now need something to build, so let’s quickly create a
APKBUILD file to build reposerve :
cd
mkdir -p reposerve/main/reposerve/home/packager/reposerve/main/reposerve/APKBUILD
# Maintainer: Eric BURGHARD <eric@itsufficient.me>
pkgname=reposerve
pkgver=0.5.0
pkgrel=0
pkgdesc="Simple alpine packages server for consuming and updating packages in CI/CD"
url="https://git.itsufficient.me/rust/reposerve"
arch="x86_64"
license="ASL 2.0"
makedepends="cargo clang-dev llvm-dev"
depends="abuild"
source="$pkgname-$pkgver.tar.gz::https://github.com/eburghar/reposerve/archive/refs/tags/$pkgver.tar.gz"
build() {
cargo build --release
}
check() {
cargo test all --release
}
package() {
install -Dm755 target/release/"$pkgname" \
"$pkgdir"/usr/bin/"$pkgname"
}
sha512sums="
d20c5fb5d9f769add480e4897f63565d8240005df9e7fa316a22a380c2eeac258e41476ec6936d0af4ea51e6f0639316448ba06c586ab08b3c321601fe3abb87 reposerve-0.5.0.tar.gz
"The directory structure is important because abuild expects that the parent of the directory
containing the APKBUILD corresponds to the name of a package repository (main).
aports project contains all the packages definitions for a
given alpine version, but I prefer building alpine packages independently of one another.
Just run :
cd /home/packager/reposerve/main/reposerve
# REPODEST is set to add an additional level
# in package directories to represent alpine version
REPODEST=~/packages/3.17 abuild -rand after a few minutes you should have the reposerve binary packaged in
/home/packager/packages/3.17/main/x86_64/reposerve-0.5.0-r0.apk
Install the package
Exit from the previous container and rerun the same podman command to fire another one :
podman run --rm --interactive --tty \
--volume "$(pwd)/tmp:/home/packager" \
alpine:3.17 shCopy the public key created in previous step in the apk keys’ directory and install the package
locally :
cp /home/packager/.abuild/*.rsa.pub \
/etc/apk/keys/
apk add /home/packager/packages/3.17/main/x86_64/reposerve-0.5.0-r0.apkYou can verify that reposerve has been installed as expected by running :
reposerve --helpreposerve 0.5.0
Usage: reposerve [-c <config>] [-d] [-v] [-a <addr>]
Simple alpine Linux packages server
Options:
-c, --config configuration file (/etc/reposerve.yaml)
-d, --dev dev mode: enable /webhook and /upload without jwt (false)
-v, --verbose more detailed output (false)
-a, --addr addr:port to bind to (0.0.0.0:8080)
--help display usage informationBuild an image
Let’s create a Containerfile inside the tmp directory which should now contains an .abuild
directory as well a packages directory that contains the package built on the previous step.
tmp/Containerfile
FROM alpine:3.17
# you should change this line to match the
# keys' filenames generated on previous step
COPY .abuild/packager-622b138b.rsa.pub \
.abuild/packager-622b138b.rsa \
/etc/apk/keys/
COPY packages/3.17/main/x86_64/reposerve-0.5.0-r0.apk \
/tmp/
RUN apk add alpine-sdk tini \
/tmp/reposerve-0.5.0-r0.apk
ENTRYPOINT ["tini", "--", "/usr/bin/reposerve"]Build the image with buildha or any other tool :
buildah bud --tag reposerve:0.5.0 tmpAnd finally test your image :
podman run --rm reposerve:0.5.0 --helpServing packages
Create a reposerve configuration file in the tmp directory :
tmp/reposerve.yaml
dir: /home/packager/packagesRun the service in dev mode :
podman run \
--rm \
--publish 8080:8080/tcp \
--volume "$(pwd)/tmp:/home/packager" \
reposerve:0.5.0 \
--config /home/packager/reposerve.yaml \
--devYour packages are now available at http://localhost:8080

It is easy to consume the packages inside a container. Without stopping the reposerve container, fire a new alpine container
podman run --rm --interactive --tty \
--volume "$(pwd)/tmp:/home/packager" \
alpine:3.17 shand do
# copy the public key
cp /home/packager/.abuild/*.rsa.pub \
/etc/apk/keys/
# add the repository (the ip address is the container's default gateway)
sed --in-place '1ihttp://10.88.0.1:8080/3.17/main' \
/etc/apk/repositories
apk update
apk add reposerve
reposerve --helpPosting packages
You can test the upload functionality by posting a package (aaudit-0.7.2-r2.apk) we will
preliminarily download from the official alpine repository. Exit the container and execute directly
from the host :
# get a random official package
version=3.17
repo=main
arch=x86_64
file=aaudit-0.7.2-r3.apk
curl -O https://dl-cdn.alpinelinux.org/alpine/v"$version"/"$repo"/"$arch"/"$file"
# publish the package to our repo using the same parameters
curl \
-F version="$version" \
-F repo="$repo" \
-F arch="$arch" \
-F file=@"$file" \
http://localhost:8080/uploadIf you go back to http://localhost:8080 you should see the new package available, and you can check that the package has been signed and the index updated.
Of course, you should deploy the apk server behind HTTPS and use JWT authorization in production. See
reposerve documentation.
Conclusion
This really was not that hard. We leveraged alpine build tools to correctly package an executable we
can now use on totally different contexts. I hope you are convinced that this approach is far
better than stacking build instructions in a Dockerfile.
What’s next
Being able to streamline the creation of packages and images for production needs obviously more work. You must for instance :
- Correctly protect and deploy the
apkkeys (I use Vault), - deploy and secure a package repository with HTTPS, and use JWT to secure your catalog,
- and set up your CI/CD pipelines.
You can find in another blog post how I overcame these challenges: A better way to build containers images

Éric BURGHARD
Related posts
Managing roles for PostgreSQL with Vault on Kubernetes
Vault has a database secret engine with a PostgreSQL driver that helps to create short-lived roles with random passwords for your database applications, but putting everything in production is not as simple as it seems.
Building an alpine golden image
How to build an alpine image to base all your containers on.
Intalling Kubernetes with cri-o inside flatcar Container Linux
How to run containers without dockershim / containerd by installing cri-o with crun under flatcar Container Linux.



