How to Create a Builder Image with S2I

Hüseyin Akdoğan
4 min readNov 20, 2018

Source-To-Image (S2I) is a standalone toolkit and workflow for creating builder images. It allows you to build reproducible Docker images from source code. The magic of the S2I is to produce ready-to-run images by injecting source code into a Docker container. This means that the builder image contains the specific intelligence required to produce that executable image based on the source code and you can have reusable dynamic images for creating build and runtime environments based on your needs.

The S2I project includes some ready-to-use builder images. You can extend these images and also create your own images.

How to Create an S2I Builder Image

An S2I builder image basically should contain assemble and run scripts besides a Docker file. In addition, it may include optional save-artifacts and usage scripts. Responsibilities of these scripts are:

  • assemble – It is responsible for the build of the application that given as input
  • run – It is responsible for running the application
  • save-artifacts – It is responsible for incremental builds
  • usage – It is responsible for printing the usage of the builder image

After downloading the application source which is given as an input, S2I streams these scripts and the source into the builder image container and then runs the assemble script.

Let’s Create Our Builder Image

Suppose we need a builder to speed up build and deployment process for our team. The builder should support Maven and Gradle projects, execute regular Jars, even be able to pass arguments when wanted, in addition, allows configuring with environment variables.

For the mentioned requirements, we must first define the Dockerfile.

FROM ubuntu:latestARG USER=1001
ARG S2IDIR="/home/s2i"
ARG APPDIR="/deployments"
LABEL maintainer="Huseyin Akdogan <hakdogan@kodcu.com>" \
io.k8s.description="S2I builder for Java Applications." \
io.k8s.display-name="Handy Environment" \
io.openshift.expose-services="8080:http" \
io.openshift.tags="builder,java,maven,gradle" \
io.openshift.s2i.scripts-url="image://$S2IDIR/bin"
COPY s2i $S2IDIR
RUN chmod 777 -R $S2IDIR
COPY jdkinstaller.sh "$APPDIR/"
COPY parse_yaml.sh "$APPDIR/"
RUN useradd $USER \
&& chown $USER:$USER $APPDIR \
&& addgroup $USER $USER \
&& chmod 777 -R $APPDIR
RUN apt-get update -y && \
apt-get install -y software-properties-common
RUN ["/bin/bash", "-c", "$APPDIR/jdkinstaller.sh"]RUN apt-get install maven -y && \
apt-get install -y unzip && \
apt-get install -y wget && \
wget https://services.gradle.org/distributions/gradle-4.10.2-bin.zip && \
mkdir /opt/gradle && \
unzip -d /opt/gradle gradle-4.10.2-bin.zip && \
ls /opt/gradle/gradle-4.10.2
ENV PATH=$PATH:/opt/gradle/gradle-4.10.2/bin
RUN rm -rf /var/lib/apt/lists/*
WORKDIR $APPDIREXPOSE 8080USER $USERCMD ["$S2IDIR/bin/run"]

We specify that we want to have JDK , Maven and Gradle in the container with the Dockerfile. The jdkinstaller.sh file contains the logic of downloading the desired version of JDK based on the related environment variable. Also, along with labels for promotion the application, we defined the location of the scripts inside of the builder image with the value of the io.openshift.s2i.scripts-url label. In the last line, we set the default CMD to execute the run script when the container was run.

Assemble script

#!/bin/bash -eset -eecho "---> Installing application source..."
cp -Rf /tmp/src/. ./
. parse_yaml.sh
if [ ! -z "$sfpath" ]; then
eval $(parse_yaml $sfpath/setting.yml "root_")
buildFileDirectory=$root_project_buildFileDirectory
execCommand=$root_project_execCommand
if [ ! -z "$buildFileDirectory" ]; then cd $buildFileDirectory; fi
fi
pomfile=pom.xml
gradlefile=build.gradle
message="---> Moving the jar file(s) to the main application directory..."
if [ -f $pomfile ]; then
echo "---> Maven build detected..."
mvn clean install -Dmaven.repo.local=/tmp/artifacts/m2 -DskipTests -Dfabric8.skip=true -Djava.net.preferIPv4Stack=true
if [ -z "$execCommand" ];
then
echo $message
mv ./target/*.jar ./
fi
elif [ -f $gradlefile ]; then
echo "---> Gradle build detected..."
gradle build
if [ -z "$execCommand" ];
then
echo message
mv ./build/libs/*.jar ./
fi
fi

As we mentioned, assemble script is responsible for the build of the application that given as input. The script contains the logic required to meet this need. By default, s2i places the application source in the/tmp/src directory. Hence, we first copy the resource files to the WORKDIR that we defined in the Dockerfile.

Then, the script looking for a build configuration file belonging to Maven or Gradle. If the file exists and the execCommand not defined, the required commands are executed and the created jar is moved to the WORKDIR. This jar will be executed by the run script.

Run script

#!/bin/bash -eset -e. parse_yaml.shif [ -z "$sfpath" ];
then
jarName="*.jar"
else
eval $(parse_yaml $sfpath/setting.yml "root_")
jarName=$root_project_jarName
arguments=${root_project_arguments//['[',']']/''}
outputDirectory=$root_project_outputDirectory
execCommandWorkDir=$root_project_execCommandWorkDir
execCommand=$root_project_execCommand
if [ -z "$jarName" ]; then jarName="*.jar"; fi
fi
if [ ! -z "$outputDirectory" ]; then cd $outputDirectory; fiif [ ! -z "$execCommand" ]; then
if [ ! -z "$execCommandWorkDir" ]; then cd $execCommandWorkDir; fi
exec $execCommand
else
java -jar $jarName $arguments
fi

The script calls the parse method for the setting.yml file if provided. The file is used to define custom requirements such as jar name or arguments. Finally, the command required to run the application is executed.

That’s it.

You can access the source code of that builder from this repository.

To Build

You should run following command.

oc new-build $builder_path_or_url \
--name jdk-container

After the build process is successful, you can pass your source code as an input to the builder image as follows.

oc new-app jdk-container~$repo_path_or_url

Conclusion

The S2I is a powerful and useful tool that can produce ready-to-run images by injecting source code into a Docker container. In addition to includes some ready-to-use builder images such as Java, or Ruby, or Python it also allows to extend these images and also create your own images. You can speed up the build and deployment processes using S2I for your team.

--

--