Building / consuming alpine Linux packages inside containers and images

15
939
0
26 min read

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 :

Build environment

We start by creating a tmp directory in the current working directory

mkdir tmp

Let’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 sh

Inside 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 sh

We 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 --install

Create 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 -r

and 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 sh

Copy 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.apk

You can verify that reposerve has been installed as expected by running :

reposerve --help
reposerve 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 information

Build an image

Let’s create a Container­file 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 tmp

And finally test your image :

podman run --rm reposerve:0.5.0 --help

Serving packages

Create a reposerve configuration file in the tmp directory :

tmp/reposerve.yaml

dir: /home/packager/packages

Run 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 \ --dev

Your packages are now available at http://localhost:8080

alpine packages server over your newly built packages

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 sh

and 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 --help

Posting 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/upload

If 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 apk keys (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

Operations

vaultpostgresk8stutorial

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.

9
939
0
40 min read

Development

alpinetutorial

Building an alpine golden image

How to build an alpine image to base all your containers on.

11
939
0
27 min read

Operations

k8sflatcartutorial

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.

12
939
0
47 min read