How to Create a Builder Image With S2I
For the eaiser crafting of Docker images from source code.
Join the DZone community and get the full member experience.
Join For Free 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 inputrun
– It is responsible for running the applicationsave-artifacts
– It is responsible for incremental buildsusage
– 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:latest
ARG 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 $APPDIR
EXPOSE 8080
USER $USER
CMD ["$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 -e
set -e
echo "---> 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 -e
set -e
. parse_yaml.sh
if [ -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; fi
if [ ! -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.
Opinions expressed by DZone contributors are their own.
Comments