Currently co-authoring a book on Docker: Get 39% off with the code 39miell
‘Source To Image’ is a means of creating Docker images by depositing source code into a separately-defined Docker image that is responsible for building the image.
You may be wondering why such a build method was conceived. The principal reason is that it allows application developers to make changes to their code without being concerned with the details of Dockerfiles, or even Docker images. If the image is delivered to a aPaaS (application platform as a service), the individual engineer need not know about Docker at all to contribute to the project! This is very useful in an enterprise environment where there are large numbers of people that have specific areas of expertise and are not directly concerned with the details of the build.
Once the process is set up, the engineer need only be concerned about the changes they want to make to their source code in order to progress them to different environments.
The advantages of this approach break down into a number of areas:
This process can easily be plugged into any existing software delivery process, and use almost any Docker image as its base layer.
This method of building can be faster than Dockerfile builds, as any number of complex operations can be added to the build process without creating a new layer at each step. S2I also gives you the capability to re-use artifacts between builds to save time.
Separation of concerns
Since source code and Docker image are cleanly and strongly separated, developers can be concerned with code while infrastructure can be concerned with Docker images and delivery. As the base underlying image is separated from the code, upgrades and patches are more easily delivered.
This process can restrict the operations performed in the build to a specific user, unlike Dockerfiles which allow arbitrary commands to be run as root.
The structure of this framework allows for a shared ecosystem of image and code separation patterns for easier large-scale operations.
This post is going to show you how to build one such pattern, albeit a simple and somewhat limited one! Our application pattern will consist of:
- Source code that contains one shell script
- A builder that creates an image which takes that shell script, makes it runnable, and runs it
Create Your Own S2I Image
1) Start up an S2I development environment
To help ensure a consistent experience you can use a maintained environment to develop your S2I build image and project.
docker run -ti -v /var/run/docker.sock:/var/run/docker.sock dockerinpractice/shutit-s2i
This command ensures the host’s docker daemon is available within the container through mounting the host’s Docker Unix socket to the container, and uses a maintained sti build environment (the image ‘dockerinpractie/shutit-s2i’)
Problems? SELinux enabled?
If you are running in an selinux-enabled environment, then you may have problems running docker within a container!
2) Create your git project
This could be on built elsewhere and placed on GitHub (for example), but to keep this example simple and self-contained we’re going to create it locally in our S2I development environment. As mentioned above, our source code consists of one shell script. As a trivial example, this simply outputs ‘Hello World’ to the terminal.
mkdir /root/myproject cd /root/myproject git init git config --global user.email "email@example.com" git config --global user.name "Your Name" cat > app.sh <<< "echo 'Hello World'" git add . git commit -am 'Initial commit'
3) Create the builder image
sti create sti-simple-shell /opt/sti-simple-shell cd /opt/sti-simple-shell
This S2I command creates several files. To get our workflow working, we’re going to focus on editing just these files:
Taking the Dockerfile first, change its contents to match the following listing:
FROM openshift/base-centos7 RUN chown -R default:default /opt/openshift COPY ./.sti/bin /usr/local/sti RUN chmod +x /usr/local/sti/* USER default
This Dockerfile uses the standard openshift base-centos7 image. This has the ‘default’ user already created within it. It then changes ownership of the default openshift code location to the default user, copies the S2I scripts into the default location for an S2I build, snsures the S2I scripts are executable and makes the builder image use the pre-created ‘default’ user by default.
Next you create the assemble script, which is responsible for taking the source code and compiling it ready to run. Below is a simplified, but feature-complete version of this bash script for you to use.
#!/bin/bash -e cp -Rf /tmp/src/. ./ chmod +x /opt/openshift/src/app.sh
It runs as a bash script, exiting on any failure (-e), copies the application source into the default directory and builds the application from source. In this case, the ‘build’ is the simple step of making the app.sh file executable.
The ‘run’ script of your S2I build is responsible for running your application. It is the script that the image will run by default:
#!/bin/bash -e exec /opt/openshift/src/app.sh
Now our builder is ready you run ‘make’ to build your S2I builder image. It will create a Docker image called sti-simple-shell. This image will provide the environment for your final image – the one that includes the software project we made above – to be built. The output of your ‘make’ call should look similar to this:
$ make imiell@osboxes:/space/git/sti-simple-shell$ make docker build --no-cache -t sti-simple-shell . Sending build context to Docker daemon 153.1 kB Sending build context to Docker daemon Step 0 : FROM openshift/base-centos7 ---> f20de2f94385 Step 1 : RUN chown -R default:default /opt/openshift ---> Running in f25904e8f204 ---> 3fb9a927c2f1 Removing intermediate container f25904e8f204 Step 2 : COPY ./.sti/bin /usr/local/sti ---> c8a73262914e Removing intermediate container 93ab040d323e Step 3 : RUN chmod +x /usr/local/sti/* ---> Running in d71fab9bbae8 ---> 39e81901d87c Removing intermediate container d71fab9bbae8 Step 4 : USER default ---> Running in 5d305966309f ---> ca3f5e3edc32 Removing intermediate container 5d305966309f Successfully built ca3f5e3edc32
If you run ‘docker images’ you should now see an image called sti-simple-shell stored locally on your host.
4) Build the Application Image
Looking back at the image at the top of this post, we now have the three things we need for an S2I build in place:
- Source code
- A builder image that provides an environment for building and running the source code
- The sti program
These three are located in one place in this walkthrough, but the only one that needs to be local to our run is the sti program. The builder image can be fetched from a registry, and the source code can be fetched from a git repository such as GitHub.
$ sti build --force-pull=false --loglevel=1 file:///root/myproject sti-simple-shell final-image-1 I0608 13:02:00.727125 00119 sti.go:112] Building final-image-1 I0608 13:02:00.843933 00119 sti.go:182] Using assemble from image:///usr/local/sti I0608 13:02:00.843961 00119 sti.go:182] Using run from image:///usr/local/sti I0608 13:02:00.843976 00119 sti.go:182] Using save-artifacts from image:///usr/local/sti I0608 13:02:00.843989 00119 sti.go:120] Clean build will be performed I0608 13:02:00.844003 00119 sti.go:130] Building final-image-1 I0608 13:02:00.844026 00119 sti.go:330] No .sti/environment provided (no evironment file found in application sources) I0608 13:02:01.178553 00119 sti.go:388] ---> Installing application source I0608 13:02:01.179582 00119 sti.go:388] ---> Building application from source I0608 13:02:01.294598 00119 sti.go:216] No .sti/environment provided (no evironment file found in application sources) I0608 13:02:01.353449 00119 sti.go:246] Successfully built final-image-1
You can now run your built image, with the source code applied to it:
$ docker run final-image-1 Hello World
Change and rebuild
It’s easier to see the purpose of this build method now we have a working example. Imagine you are a new developer ready to contribute to the project. You can simply make changes to the git repository and run a simple command to rebuild the image without knowing anything about Docker:
cd /root/myproject cat > app.sh <<< "echo 'Hello S2I!'" git commit -am 'new message' sti build --force-pull=false file:///root/myproject sti-simple-shell final-image-2
Running this image shows the new message we just set in the code:
$ docker run final-image-2 Hello S21!
This post demonstrated a simple example, but it’s easy to imagine how this framework could be adapted to your particular requirements. What you end up with is a means for developers to push changes out to other consumers of their software without caring about the details of Docker image production.
Other techniques can be used in combination with this to facilitate DevOps processes. For example, by using git post-commit hooks you can automate the S2I build call on checkin.