Everything that you never wanted to know about building containers in OBS

Dan Čermák

CC BY 4.0

who -u

Dan Čermák

Software Developer @SUSE, BCI Release Engineer
i3 SIG, Package maintainer
Developer Tools, Testing and Documentation, Home Automation
https://dancermak.name
dcermak
@Defolos@mastodon.social

Agenda

So you want to build containers?

Why use OBS?

  • automated rebuilds
  • automatic publishing
  • create official openSUSE/SLE image
  • create tiny images without (😱 horrible) hacks

Available Tools

  1. Docker & Podman/Buildah
  2. Kiwi

When to use which?

  • whatever you are familiar with
  • 🤔 undecided? → docker / podman
  • tiny image? → kiwi

When not to use OBS

  • not using zypper, dnf or apt
  • complex Dockerfile, curl-ing from the www
  • require previous releases to be available
  • very unfamiliar with OBS

Minimal Docker Example

Repository Setup

from devel:BCI:Tumbleweed:

  <repository name="standard">
    <path project="openSUSE:Factory" repository="images"/>
    <path project="openSUSE:Factory:ARM" repository="images"/>
    <path project="openSUSE:Factory:ARM" repository="standard"/>
    <path project="openSUSE:Factory:PowerPC" repository="standard"/>
    <path project="openSUSE:Factory:zSystems" repository="standard"/>
    <path project="openSUSE:Factory" repository="snapshot"/>
    <arch>x86_64</arch>
    <arch>aarch64</arch>
    <arch>s390x</arch>
    <arch>ppc64le</arch>
  </repository>
  <!-- snip -->
  <repository name="containerfile">
    <path project="$THIS_PROJECT" repository="images"/>
    <path project="$THIS_PROJECT" repository="standard"/>
    <arch>x86_64</arch>
    <arch>aarch64</arch>
    <arch>s390x</arch>
    <arch>ppc64le</arch>
  </repository>
</repository>

based on openSUSE:Templates:Images:Tumbleweed:

<project name="$basename:Images:Tumbleweed">
  <!-- snip -->
  <repository name="containers_s390x" rebuild="local">
    <path project="openSUSE:Templates:Images:Tumbleweed" repository="containers_s390x"/>
  </repository>
  <repository name="containers_ppc" rebuild="local">
    <path project="openSUSE:Templates:Images:Tumbleweed" repository="containers_ppc"/>
  </repository>
  <repository name="containers_arm" rebuild="local">
    <path project="openSUSE:Templates:Images:Tumbleweed" repository="containers_arm"/>
  </repository>
  <repository name="containers" rebuild="local">
    <path project="openSUSE:Templates:Images:Tumbleweed" repository="containers"/>
    <arch>x86_64</arch>
  </repository>
</project>

prjconf

%if %_repository == "containerfile"
Type: docker
# optional:
BuildEngine: podman
%endif

Dockerfile

FROM opensuse/tumbleweed:latest
#!BuildTag: opensuse/git:latest
RUN zypper -n in git
CMD ["/usr/bin/git"]

Dockerfile peculiarities

  • no network access
  • all layers squashed
  • installs & removes obs-docker-support
  • USER must be root
  • all zypper/dnf calls must be in Dockerfile

Kiwi example

<image schemaversion="6.5" name="$name-image"
       xmlns:suse_label_helper="com.suse.label_helper">
  <!-- snip -->
  <preferences>
    <type image="docker"
	  derived_from="obsrepositories:/suse/sle15#15.3">
      <containerconfig
	  name="bci/ruby"
	  tag="2.5"
	  maintainer="SUSE LLC (https://www.suse.com/)"
	  additionaltags="2.5-%RELEASE%,2,2-%RELEASE%">
      </containerconfig>
    </type>
    <version>15.3.0</version>
  </preferences>
  <!-- snip -->
</image>

Repository setup

based on devel:BCI:Tumbleweed:

<repository name="standard">
  <path project="openSUSE:Factory" repository="images"/>
  <path project="openSUSE:Factory:ARM" repository="images"/>
  <path project="openSUSE:Factory:ARM" repository="standard"/>
  <path project="openSUSE:Factory:PowerPC" repository="standard"/>
  <path project="openSUSE:Factory:zSystems" repository="standard"/>
  <path project="openSUSE:Factory" repository="snapshot"/>
  <arch>x86_64</arch>
  <arch>aarch64</arch>
  <arch>s390x</arch>
  <arch>ppc64le</arch>
</repository>
<repository name="images">
  <path project="devel:BCI:Tumbleweed" repository="containerfile"/>
  <path project="devel:BCI:Tumbleweed" repository="standard"/>
  <arch>x86_64</arch>
  <arch>aarch64</arch>
  <arch>s390x</arch>
  <arch>ppc64le</arch>
</repository>

based on openSUSE:Templates:Images:Tumbleweed:

<project name="$prefix:Images:Tumbleweed">
  <!-- snip -->
  <repository name="images_s390x" rebuild="local">
    <path project="openSUSE:Templates:Images:Tumbleweed" repository="images_s390x"/>
  </repository>
  <repository name="images_ppc" rebuild="local">
    <path project="openSUSE:Templates:Images:Tumbleweed" repository="images_ppc"/>
  </repository>
  <repository name="images_arm" rebuild="local">
    <path project="openSUSE:Templates:Images:Tumbleweed" repository="images_arm"/>
  </repository>
  <repository name="images" rebuild="local">
    <path project="openSUSE:Containers:Tumbleweed" repository="containers"/>
    <arch>x86_64</arch>
  </repository>
</project>

prjconf

%if "%_repository" == "images"
Type: kiwi
Repotype: none
Patterntype: none
%endif

Registry Frontend

registry.opensuse.org/ + ${prj_name/:/\/}.lower() + /$REPO/$BUILD_TAG

Tagging Images

docker build -t my/prefix:1.5 -t my/prefix:latest .

Dockerfile:

#!BuildTag: my/prefix:1.5
#!BuildTag: my/prefix:latest

kiwi xml:

<!-- OBS-AddTag: my/prefix:1.5 my/prefix:latest -->
<!-- snip -->
      <containerconfig
	  name="my/prefix"
	  tag="1.5"
	  additionaltags="latest">
      </containerconfig>
<!--snip-->

Local Testing

❯ osc build --clean images
# *snip*
/var/tmp/build-root/images-x86_64/usr/src/packages/KIWI/registry-image.x86_64-2023-Build.docker.tar
/var/tmp/build-root/images-x86_64/usr/src/packages/KIWI/registry-image.x86_64-2023-Build.docker.tar.sha256

❯ podman load -i /var/tmp/build-root/images-x86_64/usr/src/packages/KIWI/registry-image.x86_64-2023-Build.docker.tar
Getting image source signatures
# *snip*
Storing signatures
Loaded image: docker.io/opensuse/registry:2.8
Loaded image: docker.io/opensuse/registry:2.8-
Loaded image: docker.io/opensuse/registry:latest

❯ podman run --rm -it docker.io/opensuse/registry:2.8

Multi-Arch

  • one Dockerfile for all architectures
  • exclude/build only on architectures:
#!ExclusiveArch: x86_64 aarch64
#!ExcludeArch: s390x ppc64le
  • add tricks like:
RUN [ $(uname -m) = "x86_64" ] && zypper -n in amd64-only-pkg
  • exclude/include lines for scheduler:
#!ArchExclusiveLine x86_64
RUN [ $(uname -m) = "x86_64" ] && zypper -n in amd64-only-pkg
#!ArchExcludedLine x86_64
RUN [ $(uname -m) = "x86_64" ] || zypper -n in non-amd64-pkg

Building against a Registry

  • create the registry as a dod project/repository (e.g. SUSE:Registry):
<project name="home:$username:registry">
  <!-- snip -->
  <publish><disable/></publish>
  <repository name="standard">
    <download arch="x86_64"
	      url="https://registry.suse.com"
	      repotype="registry"/>
    <arch>x86_64</arch>
  </repository>
</project>
  • Add it to your project _meta:
<project name="home:$username:containers">
  <!-- snip -->
  <repository name="standard">
    <path project="home:$username:registry"
	  repository="standard"/>
    <!-- additional paths -->
    <arch>x86_64</arch>
    <arch>aarch64</arch>
  </repository>
</project>

have choice for

❯ osc buildinfo -d $prj $pkg containers x86_64|grep container
undecided about (direct):container:bci/openjdk:11:
    container:bci-openjdk-11@devel:BCI:SLE-15-SP4/containerfile
    container:bci_openjdk:11@SUSE:Registry/standard
Prefer: -container:bci_openjdk:11
# or
Prefer: -container:bci-openjdk-11

Helper services

RUN curl -f https://path/to/binary.tar.gz -o binary.tar.gz

replace with:

#!RemoteAssetUrl: https://path/to/binary.tar.gz
COPY binary.tar.gz .

or

<!-- OBS-RemoteAsset: https://path/to/binary.tar.gz -->

remote assets documentation

replace_using_package_version

tag & set env vars from package versions

source: openSUSE/obs-service-replace_using_package_version

#!BuildTag: opensuse/389-ds:%%389ds_version%%
<services>
  <service name="replace_using_package_version"
	   mode="buildtime">
    <param name="file">Dockerfile</param>
    <param name="regex">%%389ds_version%%</param>
    <param name="package">389-ds</param>
    <param name="parse-version">minor</param>
  </service>
</services>

metainfo_helper

  • source: kiwi_metainfo_helper
  • %RELEASE%<cicnt\>.<bldcnt\>
  • %OS_VERSION% ⇒ from /etc/os-release
  • replaces in build recipe (kiwi.xml, Dockerfile, Chart.yaml)
#!BuildTag: bci/bci-init:%OS_VERSION_ID_SP%
LABEL org.opencontainers.image.created="%BUILDTIME%"
LABEL org.opencontainers.image.source="%SOURCEURL%"
LABEL org.opensuse.reference="registry.suse.com/bci/bci-init:%OS_VERSION_ID_SP%.%RELEASE%"
LABEL org.openbuildservice.disturl="%DISTURL%"

replace_using_env

  • replaces %%VARNAME%% with $VARNAME from the build environment
  • can run scripts before the build (→ to set the env)
#!BuildTag: opensuse/virt-operator:%%PKG_VERSION%%-%%PKG_RELEASE%%
ENV KUBEVIRT_VERSION=%%PKG_VERSION%%
<services>
  <service mode="buildtime" name="replace_using_env">
    <param name="file">Dockerfile</param>
    <param name="var">PKG_VERSION</param>
    <param name="var">TAGPREFIX</param>
    <param name="eval">/path/to/my/script/here</param>
  </service>
</services>
  • : anything from _prjconf can be evaluated via rpm -E %macro

Labels

preserve LABEL them using kiwi_label_helper:

<labels>
  <suse_label_helper:add_prefix prefix="org.opensuse.tiny">
    <label name="org.opencontainers.image.title"
	   value="openSUSE Leap Base Container"/>
  </suse_label_helper:add_prefix>
</labels>

or docker_label_helper:

# labelprefix=org.opensuse.tiny
LABEL org.opencontainers.image.title=openSUSE Leap Base Container"
# endlabelprefix

expands to:

"Labels": {
  "org.opencontainers.image.title": "openSUSE Leap Base Container",
  "org.opensuse.tiny.title": "openSUSE Leap Base Container"
}

Build Arguments

ARG VERSION
ARG DEFAULT_USER=me
buildah bud --build-arg="VERSION=1.4.2" .
docker build --build-arg="VERSION=1.4.2" .

osc meta prjconf

BuildFlags: dockerarg:VERSION=1.4.2

Even more 🧙🪄?

#!BuildName: NAME
#!BuildVersion: VERSION
#!NoSquash
ExpandFlags: kiwi-nobasepackages

Keeping multiple versions around

rebuild repository:

<project name=":Rebuild">
  <!-- snip -->
  <repository name="images">
    <releasetarget project=":Release" repository="containers" trigger="manual"/>
    <!-- ordinary paths here -->
    <arch>x86_64</arch>
    <arch>aarch64</arch>
  </repository>
</project>

release repository:

<project name=":Release" kind="maintenance_release">
  <!-- snip -->
  <build><disable/></build>
  <publish><enable/></publish>
  <repository name="containers">
    <path project="openSUSE:Tumbleweed" repository="standard"/>
    <arch>x86_64</arch>
    <arch>aarch64</arch>
  </repository>
</project>

release:

osc release :Rebuild $image_name

What else is there?

Demo!

What would you like to see?

Questions?

Answers!