Singularity 2.4 introduces the ability to run “container instances”, allowing you to run services (e.g. Nginx, MySQL, etc…) using Singularity. A container instance, simply put, is a persistent and isolated version of the container image that runs in the background.

Why container instances?

Let’s say I want to run a web server. With nginx, that is pretty simple, I install nginx and start the service:

apt-get update && apt-get install -y nginx
service nginx start

With older versions of Singularity, if you were to do something like this, from inside the container you would happily see the service start, and the web server running! But then if you were to log out of the container what would happen?

Orphan process within unreachable namespaces!

You would lose control of the process. It would still be running, but you couldn’t easily kill or interface with it. This is a called an orphan process. Singularity versions less than 2.4 were not designed to handle running services properly.

Container Instances in Singularity

With Singularity 2.4 and the addition of container instances, the ability to cleanly, reliably, and safely run services in a container is here. First, let’s put some commands that we want our instance to execute into a script. Let’s call it a startscript. This fits into a definition file like so:

%startscript

service nginx start

Now let’s say we build a container with that startscript into an image called nginx.img and we want to run an nginx service. All we need to do is start the instance with the instance.start command, and the startscript will run inside the container automatically:

              [command]        [image]    [name of instance]
$ singularity instance.start   nginx.img  web

When we run that command, Singularity creates an isolated environment for the container instances’ processes/services to live inside. We can confirm that this command started an instance by running the instance.list command like so:

$ singularity instance.list
INSTANCE NAME    PID      CONTAINER IMAGE
web              790      /home/mibauer/nginx.img

If we want to run multiple instances from the same image, it’s as simple as running the command multiple times. The instance names are an identifier used to uniquely describe an instance, so they cannot be repeated.

$ singularity instance.start   nginx.img  web1
$ singularity instance.start   nginx.img  web2
$ singularity instance.start   nginx.img  web3

And again to confirm that the instances are running as we expected:

$ singularity instance.list
INSTANCE NAME    PID      CONTAINER IMAGE
web1             790      /home/mibauer/nginx.img
web2             791      /home/mibauer/nginx.img
web3             792      /home/mibauer/nginx.img

If the service you want to run in your instance requires a bind mount, then you must pass the -B option when calling instance.start. For example, if you wish to capture the output of the web1 container instance which is placed at /output/ inside the container you could do:

$ singularity instance.start -B output/dir/outside/:/output/ nginx.img  web1

If you want to poke around inside of your instance, you can do a normal singularity shell command, but give it the instance URI:

$ singularity shell instance://web1
Singularity: Invoking an interactive shell within container...

Singularity pdf_server.img:~/> 

Similarly, you can use the singularity run/exec commands on instances:

$ singularity run instance://web1
$ singularity exec instance://web1 ps -ef

When using run with an instance URI, the runscript will be executed inside of the instance. Similarly with exec, it will execute the given command in the instance.

When you are finished with your instance you can clean it up with the instance.stop command like so:

$ singularity instance.stop web1

If you have multiple instances running and you want to stop all of them, you can do so with a wildcard or the -a flag:

$ singularity instance.stop \*
$ singularity instance.stop -a

Note that you must escape the wildcard with a backslash like this \* to pass it properly.

Nginx “Hello-world” in Singularity

Let’s take a look at setting up a sample nginx web server using instances in Singularity. First we will just create a basic definition file:

Bootstrap: docker
From: nginx
Includecmd: no

%startscript
	nginx

All this does is download the official nginx Docker container, convert it to a Singularity image, and tell it to run nginx when you start the instance. Since we’re running a web server, we’re going to run the following commands as root.

# singularity build nginx.img Singularity
# singularity instance.start nginx.img web1

Just like that we’ve downloaded, built, and ran an nginx Singularity image. And to confirm that it’s correctly running:

$ curl localhost
127.0.0.1 - - [06/Oct/2017:21:46:43 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.47.0" "-"
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Putting it all together

In this section, we will demonstrate an example of packaging a service into a container and running it. The service we will be packaging is an API server that converts a web page into a PDF, and can be found here. The final example can be found here on GitHub, and here on SingularityHub. If you wish to just download the final image directly from Singularity Hub, simply run singularity pull shub://bauerm97/instance-example.

Building the Image

To begin, we need to build the image. When looking at the GitHub page of the url-to-pdf-api, we can see that it is a Node 8 server that uses headless Chromium called Puppeteer. Let’s first choose a base from which to build our container, in this case I used the docker image node:8 which comes pre-installed with Node 8:

Bootstrap: docker
From: node:8
Includecmd: no

Puppeteer also requires a few dependencies to be manually installed in addition to Node 8, so we can add those into the post section as well as the installation script for the url-to-pdf-api:

%post
     apt-get update
     apt-get install -yq gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 \
     libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 \
     libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 \
     libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 \
     libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates \
     fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget curl
     rm -r /var/lib/apt/lists/*
     cd /
     git clone https://github.com/alvarcarto/url-to-pdf-api.git pdf_server
     cd pdf_server
     npm install
     chmod -R 0755 .

And now we need to define what happens when we start an instance of the container. In this situation, we want to run the commands that starts up the url-to-pdf-api server:

%startscript
    cd /pdf_server
    # Use nohup and /dev/null to completely detach server process from terminal
    nohup npm start > /dev/null 2>&1 < /dev/null &

Also, the url-to-pdf-api server requires some environment variables be set, which we can do in the environment section:

%environment
    NODE_ENV=development
    PORT=8000
    ALLOW_HTTP=true
    URL=localhost
    export NODE_ENV PORT ALLOW_HTTP URL

Now we can build the definition file into an image! Simply run build and the image will be ready to go:

$ sudo singularity build url-to-pdf-api.img Singularity

Running the Server

Now that we have an image, we are ready to start an instance and run the server:

$ singularity instance.start url-to-pdf-api.img pdf

We can confirm it’s working by sending the server an http request using curl:

$ curl -o google.pdf localhost:8000/api/render?url=http://google.com
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 51664  100 51664    0     0  12443      0  0:00:04  0:00:04 --:--:-- 12446

If you shell into the instance, you can see the running processes:

$ singularity shell instance://pdf 
Singularity: Invoking an interactive shell within container...

Singularity pdf_server.img:~/bauerm97/instance-example> ps auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
node        87  0.2  0.0  20364  3384 pts/0    S    16:16   0:00 /bin/bash --norc
node        88  0.0  0.0  17496  2144 pts/0    R+   16:16   0:00  \_ ps auxf
node         1  0.0  0.0  13968  1904 ?        Ss   16:10   0:00 singularity-instance: mibauer [pdf]
node         3  0.1  0.4 997452 40364 ?        Sl   16:10   0:00 npm                          
node        13  0.0  0.0   4340   724 ?        S    16:10   0:00  \_ sh -c nodemon --watch ./src -e j
node        14  0.0  0.4 1184492 37008 ?       Sl   16:10   0:00      \_ node /scif/apps/pdf_server/p
node        26  0.0  0.0   4340   804 ?        S    16:10   0:00          \_ sh -c node src/index.js
node        27  0.2  0.5 906108 43424 ?        Sl   16:10   0:00              \_ node src/index.js
Singularity pdf_server.img:~/bauerm97/instance-example> ls
LICENSE  README.md  Singularity  out  pdf_server.img
Singularity pdf_server.img:~/bauerm97/instance-example> exit

Making it Pretty

Now that we have confirmation that the server is working, let’s make it a little cleaner. It’s difficult to remember the exact curl command and URL syntax each time you want to request a PDF, so let’s automate that. To do that, we’re going to be using Standard Container Integration Format (SCIF) apps, which are integrated directly into singularity. If you haven’t already, check out the Singularity app documentation to come up to speed.

First off, we’re going to move the installation of the url-to-pdf-api into an app, so that there is a designated spot to place output files. To do that, we want to add a section to our definition file to build the server:

%appinstall pdf_server
    git clone https://github.com/alvarcarto/url-to-pdf-api.git pdf_server
    cd pdf_server
    npm install
    chmod -R 0755 .

And update our startscript to point to the app location:

%startscript
    cd "${APPROOT_pdf_server}/pdf_server"
    # Use nohup and /dev/null to completely detach server process from terminal
    nohup npm start > /dev/null 2>&1 < /dev/null &

Now we want to define the pdf_client app, which we will run to send the requests to the server:

%apprun pdf_client
    if [ -z "${1:-}" ]; then
        echo "Usage: singularity run --app pdf <instance://name> <URL> [output file]"
        exit 1
    fi
    curl -o "${SINGULARITY_APPDATA}/output/${2:-output.pdf}" "${URL}:${PORT}/api/render?url=${1}"

As you can see, the pdf_client app checks to make sure that the user provides at least one argument. Now that we have an output directory in the container, we need to expose it to the host using a bind mount. Once we’ve rebuilt the container, make a new directory callout out for the generated PDF’s to go. Now we simply start the instance like so:

$ singularity instance.start -B out/:/scif/data/pdf_client/output/ url-to-pdf-api.img pdf

And to request a pdf simply do:

$ singularity run --app pdf_client instance://pdf http://google.com google.pdf

And to confirm that it worked:

$ ls out/
google.pdf

When you are finished, use the instance.stop command to close all running instances.

$ singularity instance.stop \*

Important Notes

  • The instances are linked with your user. So if you start an instance with sudo, that is going to go under root, and you will need to call sudo singularity instance.list in order to see it.