Singularity Container Instances

These docs are for Singularity Version 2.5.1. For older versions, see our archive

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:

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:

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.

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:

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.

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:0auto;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
<ahref="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<ahref="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:

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:

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: