Inspect and extend containers, modify definition files, and create read-only containers for security.

Tips and Tricks for Containers

Various aspects of containers make them more useful to you. The following discussion of these topics have no theme; I just look at various properties of containers that I think are worthwhile knowing. In the following sections, I focus on Singularity and Docker.

Making containers read-only can make them resistant to attacks, such as gaining access to a container to add or delete code to create an exploit. Read-only containers also prevent the hacker from modifying the files inside. Although it can prevent attacks, the read-only state limits anyone from doing anything with the container. A read-only designation does not just help with security, it also helps ensure that the containers are the same every time. Because the containers are locked down so they cannot be modified, they execute the same way every time (continuity of execution).

Docker Read-Only Containers

By default, Docker containers are read-write. Because users of the container have elevated privileges, they can modify the container when it is running. However, without saving the container, any container changes are lost once the container is stopped and erased from memory. In a subsequent section in this article I discuss how changes can be saved – also referred to as "extending" the container.

In Docker you can use the --read-only option to disable writing to the container, such as:

$ sudo docker run --gpus all -it --name testing \
  -u $(id -u):$(id -g) -e HOME=$HOME \
  -e USER=$USER -v $HOME:$HOME \
  --rm  --read-only nvidia/cuda:10.1-base-ubuntu18.04

When running a read-only container, it likely will need to access local files (i.e., files in the container, like the filesystems /var, /etc, or /run). The Docker option --tmpfs creates a temporary filesystem in memory that can be used for reading and writing.

For example, if you want to make /var writable, you could use the --tmpfs /var option. You can use as many --tmpfs options as you need on the command line. This option is useful because it does not write a volume back to the Docker host.

If you run the Docker container as read-only, you still might have to have some read-write filesystems (e.g., /tmp and /dev). Fortunately, Docker has an option for creating and using temporary read-write filesystems that you can use at runtime.

An example of a docker run command that creates a writable filesystem for /var and /etc looks like:

$ sudo docker run --gpus all -it --name testing \
  -u $(id -u):$(id -g) -e HOME=$HOME \
  -e USER=$USER -v $HOME:$HOME \
  --rm  --read-only --tmpfs /var \
  --tmpfs /etc nvidia/cuda/10.1-ubuntu:18.04

Notice that both /var and /etc use --tmpfs. These temporary mountpoints use host memory. When the container stops, these filesystems are removed and any files that are in these filesystems are not saved.

Other options you can use with --tmpfs better refines a specific filesystem mount:

$ sudo docker run --gpus all -it --name testing \
  -u $(id -u):$(id -g) -e HOME=$HOME \
  -e USER=$USER -v $HOME:$HOME \
  --rm  --read-only --tmpfs /var \
  --tmpfs /etc nvidia/cuda/10.1-ubuntu:18.04

In this case, /etc is obviously being created read-write (rw), with no set UID (nosuid), no execution (noexec), and a fixed size of 2GB (2g). For more options, check out the tmpfs docs.

A great combination that achieves a true read-only situation is to run Docker containers as read-only while using temporary filesystems.

Singularity Read-Only Containers

By default, Singularity container filesystems are read-only, so you do not have to do anything to attain that state. Pretty simple.