Skip to content

How to change container behavior

1. How to change the filesystems a container sees

By default, a singularity container running on LC will see your home directory and its contents, but not other filesystems such our /p/lustre# filesystems and our /usr/workspace filesystems. For example,

janeh@oslic9:~/Singularity$ singularity shell my_julia.img Singularity> pwd /g/g0/janeh/Singularity Singularity> ls /p/lustre1/janeh ls: cannot access '/p/lustre1/janeh': No such file or directory

You can change this by binding or mounting a particular directory path in your container via the --bind or -B flag.

janeh@oslic9:~/Singularity$ singularity shell -B /p/lustre1/janeh my_julia.img Singularity> ls /p/lustre1/janeh 0_LC_AutoDelete GaAs

Note that binding multiple directory paths in your container requires using the -B flag multiple times, for example,

singularity shell -B /p/lustre1/janeh -B /usr/workspace/janeh my_julia.img

The -B or --bind flag can follow any of the singularity commands mentioned above, and should precede the name of the container with which you are working. For example, you might use -B with singularity exec or singularity run as in

singularity exec --bind /p/lustre1/janeh my_julia.img ls /p/lustre1/janeh

or

singularity run -B /p/lustre1/janeh my_julia.img

Note: Symlinks can create ambiguity as to where to find a directory you might like to mount. For example, /usr/workspace/<username> on LC systems links either to /usr/WS1/<username> or /usr/WS2/<username>, depending on the username. Binding /usr/workspace/<your-username> to your container will work, but if you simply try to bind /usr/workspace, you may not be able to see your workspace directory. (Imagine your workspace lives in /usr/WS1 and binding /usr/workspace mounts /usr/WS2.)

2. How to change a container's runscript

Sometimes we’ll find ourselves wanting to change the contents of a container or the behavior exhibited by our container when it is run. For example, maybe I want a container that uses Julia to remind me of the value of pi at runtime, rather than to simply start the interpreter.

Let’s create a file in our working directory called calc_pi.jl which contains

"""
     function calc_pi(N)

This function calculates pi with a Monte Carlo simulation using N samples.
"""
function calc_pi(N)
    # Generate `N` pairs of x,y coordinates on a grid defined 
    # by the extrema (1, 1), (-1,-1 ), (1, -1), and (-1, 1) 
    samples = rand([1, -1], N, 2) .* rand(N, 2)
    # how many of these sample points lie within the circle
    # of max size bounded by the same extrema
    samples_in_circle = sum([sqrt(samples[i, 1]^2 + samples[i, 2]^2) < 1.0 for i in 1:N])

    pi = 4*samples_in_circle/N
end

# print the estimate of pi calculated with 10,000 samples 
println(calc_pi(10_000))

Then update that file’s permissions so it’s broadly accessible for reading, writing, and executing via chmod 777 calc_pi.jl

Now we’ll show a couple ways to build a container that runs a copy of this file by default.

Sandboxes

Using sandboxes is one way to edit a container and its behavior, though editing a container recipe (below) is the preferred method. In using a sandbox, we will typically

  1. Create a sandbox from an image with singularity build
  2. Change the contents of the container by editing within the sandbox
  3. Write a new image from the sandbox, again with singularity build

First, you can create a sandbox using singularity build with the --sandbox flag: For example,

singularity build --sandbox julia_sandbox/ my_julia.img

creates a directory called julia_sandbox inside my working directory. Now,

ls julia_sandbox

returns the same results as

singularity exec my_julia.img ls /

Second, let’s change the contents of the sandbox inside julia_sandbox/. Now copy calc_pi.jl inside my sandbox’s /opt/ directory:

cp ./calc_pi.jl julia_sandbox/opt/

Now I’ll update the runscript in /singularity to read

#!/bin/sh
# if there are no command line arguments, run the calc_pi.jl script 
if [ -z "$@" ]; then
    julia /opt/calc_pi.jl
# otherwise, there may be command line arguments provided -- such as a julia script
else
    julia "$@" 
fi

so that we run calc_pi.jl and print our estimate for pi whenever we run this container. Finally, I can create an updated container image, my_updated_julia.img, from the edited sandbox via

singularity build my_updated_julia.img julia_sandbox/

Now, when I run the new container via ./my_updated_julia.img, an approximation to pi prints to stdout.

Container recipes

Rather than creating a sandbox from an image, manually editing that sandbox, and then creating a new image from the altered sandbox, a better documented and more reproducible way to create a new container image is to use a container recipe. We recommend using a Dockerfile as your recipe file and building as described in "How to pull or build a container".