<- previous - home - next ->


The Runscript: Making Containerized Apps Behave Like Normal Apps

Consider an application that takes one file as input, analyzes the data in the file, and produces another file as output. This is obviously a very common situation.

Let’s imagine that we want to use the cowsay program in our lolcow.sif to “analyze data”. We should give our container an input file, it should reformat the text (in the form of a cow speaking), and it should dump the output into another file.

Here’s an example. First I’ll make some “data”

$ echo "The grass is always greener over the septic tank" > /data/input

Now I’ll “analyze” the “data”

$ cat /data/input | singularity exec lolcow.sif cowsay >/data/output

The “analyzed data” is saved in a file called /data/output.

$ cat /data/output
 ______________________________________
/ The grass is always greener over the \
\ septic tank                          /
 --------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

This works… but the syntax is ugly and difficult to remember.

Singularity supports a neat trick for making a container function as though it were an executable. We need to create a runscript inside the container. It turns out that our Singularity recipe file already contains a runscript. It causes our container to print a helpful message.

$ ./lolcow.simg
This is what happens when you run the container...

Let’s rewrite this runscript in the definition file and rebuild our container so that it does something more useful. And while we’re at it, we’ll change the bootstrap method to library

BootStrap: library
From: debian:9

%runscript
    if [ $# -ne 2 ]; then
        echo "Please provide an input and an output file."
        exit 1
    fi
    cat $1 | cowsay > $2

%post
    apt-get update
    apt-get -y install fortune cowsay lolcat

%environment
    export PATH=$PATH:/usr/games

Now we must rebuild out container to install the new runscript.

$ sudo singularity build --force lolcow.sif lolcow.def

Note the --force option which ensures our previous container is completely overwritten.

After rebuilding our container, we can call the lolcow.sif as though it were an executable, give it input and output file names.

$ ./lolcow.sif /data/input /data/output2
/.singularity.d/runscript: 7: /.singularity.d/runscript: cannot create /data/output2: Directory nonexistent
cat: /data/input: No such file or directory

Whoops!

We are no longer piping redirecting standard output into and out of the container, so we need to bind mount the /data directory into the container. It will be convenient to simply set the bind path as an environment variable.

$ export SINGULARITY_BINDPATH=/data

$ ./lolcow.sif /data/input /data/output2

$ ./lolcow.sif /data/vader.txt /data/output3

$ cat /data/output2 /data/output3
 ______________________________________
/ The grass is always greener over the \
\ septic tank                          /
 --------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
 __________________
< I am your father >
 ------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

To summarize, we have written a runscript for our container that will do some very basic error checking and expects the location of an input file and an output file allowing it to analyze the data. This is obviously a trivial example, but the sky is the limit. If you can code it, you can make your container do it!


BONUS

You will often see this or something similar as a containers runscript.

%runscript
    python "$@"

What does this do?


<- previous - home - next ->