silo

silo makes and enters containers. A container is a directory under /rod/containers with its own package profile, home and /etc. It runs under bubblewrap in new namespaces, shares /nix/store with the host and uses the kernel of the host. For a one-command sandbox that keeps nothing, use hazmat. security lists what the isolation covers.

silo is part of the uranium-tools package. It creates and deletes containers as root, so the examples use the root prompt.

Synopsis

silo
silo [-n] NAME [PACKAGE...] [-- COMMAND...]
silo -d NAME...
silo -h
silo
Lists the containers, one name a line, sorted.
silo NAME
Opens a shell in the container NAME. If it does not exist it is created first with bashInteractive and coreutils.
silo NAME PACKAGE...
Installs the packages in the container, creating it first if needed, then opens a shell. Giving packages for an existing container installs them in it.
silo NAME [PACKAGE...] -- COMMAND...
Runs the command instead of a shell. Everything after the first -- is the command, and everything before it after the name is a package.
-n
Gives this run no network. It has to come first, before the name, and it is not stored, so the next silo NAME without -n has the network again. Without -n the container shares the network of the host, with it's addresses and ports.
-d NAME...
Deletes the containers one by one. A failure stops the run, so the names before it are already gone.

Names and packages

A name has the characters a to z, 0 to 9, - and _. A package is a name in the package index at /rod/nixpkgs (an attribute such as nginx or python3), or a recipe. When /rod/recipes/NAME.nix exists that file is installed and the index is not asked, see recipes. Creating a container or installing into one needs the index, and rod +s makes it.

What creation does

  1. Takes a scram frame labelled pre-container when scram is on the PATH and ROD_INNER is not set. A failed frame is ignored.
  2. Makes /rod/containers/NAME/home and /rod/containers/NAME/etc.
  3. For a container without a profile, puts bashInteractive and coreutils in front of the packages you gave.
  4. Installs every package that has a recipe with its own nix-env call, then all the others in one call. The output of nix-env is not shown.
  5. A container that already existed is kept as it was.
  6. Writes four files in the container etc: passwd with the single line root:x:0:0:root:/root:/rod/profile/bin/bash, group with root:x:0:, hostname with the name and hosts with 127.0.0.1 localhost NAME. They are written again whenever packages are given.

What entering does

silo resolves the profile link of the container to its store path, then replaces itself with bubblewrap. The command is bash by default. The call, with PROFILE the resolved store path of the profile and C the container directory:

bwrap --unshare-all [--share-net] --die-with-parent --hostname NAME
  --ro-bind /nix /nix
  --ro-bind PROFILE /rod/profile
  --dir /usr
  --symlink rod/profile/bin /bin
  --symlink ../rod/profile/bin /usr/bin
  --symlink rod/profile/bin /sbin
  --symlink rod/profile/lib /lib
  --bind C/etc /etc
  --ro-bind /etc/resolv.conf /etc/resolv.conf
  --ro-bind /etc/ssl /etc/ssl
  --bind C/home /root
  --proc /proc --dev /dev --tmpfs /tmp
  --setenv HOME /root --setenv PATH /rod/profile/bin --setenv PS1 PROMPT
  --chdir /root
  /rod/profile/bin/sh -c COMMAND

--share-net is there unless -n was given. PROMPT shows the container name in reverse video. --unshare-all makes new user, mount, PID, IPC, UTS and cgroup namespaces and a new network namespace that --share-net keeps shared with the host. --die-with-parent kills the process when the parent of silo exits.

What the process sees

/nixThe whole host store, read-only. Every store path of the host is readable in the container, not only those in its profile.
/rod/profile, /bin, /usr/bin, /sbin, /libThe container profile, read-only. /lib links into it. A prebuilt binary that expects /lib64/ld-linux-x86-64.so.2 does not start unless its libraries are in the profile.
/etcThe container etc directory, writable, with the four files above. /etc/resolv.conf and /etc/ssl of the host are mounted over it read-only.
/rootThe container home directory, writable. This is $HOME and the working directory.
/tmp, /proc, /devA new memory filesystem, a procfs of the new PID namespace and a minimal /dev.
Everything elseNot present. The host /home, /rod except the profile, /var, /run, /sys and /boot do not exist in the container.

The user and group id inside are those of the caller, because silo passes no --uid or --gid. When run as root the process is root of the host, and the user namespace only limits what that root can do to the kernel and other namespaces. The container shares the kernel, so a kernel bug reaches the host. See security.

The command line

silo joins the words of the command with single spaces and gives the result to sh -c. The shell of the container reads it again, so quoting is lost:

# silo box -- echo "a   b"          prints a b
# silo box -- "echo 'a   b'"        prints a   b

The exit status of the command is the exit status of silo.

Examples

# silo web nginx
# silo web -- nginx -v
# silo web git curl
# silo -n scratch gcc
# silo
scratch
web
# silo -d scratch web

The first line creates web with nginx and opens a shell. The second runs one command in it. The third adds packages to the same container. The fourth makes a container without network and opens a shell in it. A shell in a container shows the name in the prompt.

Deleting

silo -d NAME checks the name, takes a frame labelled pre-container-remove and removes the directory with its profile, home and etc. It does not touch the store or run garbage collection, so the packages stay on disk until the store is cleaned. Errors:

A frame brings back a deleted container, because /rod/containers is in the root subvolume that frames hold: scram restore /rod/containers/NAME FRAME puts it back.

Files and variables

/rod/containers/NAME/profile
The nix-env profile of the container, with its generation links profile-N-link.
/rod/containers/NAME/home, /rod/containers/NAME/etc
Mounted as /root and /etc inside.
/rod/recipes/NAME.nix
A recipe that replaces the package of the same name.
ROD_ROOT
Replaces /rod for the package index, the recipes and the container directory. The paths /rod/profile inside the container do not change, and the tools add $ROD_ROOT/profile/bin to the PATH.
ROD_INNER
When set, silo takes no frames. rod sets it for its own calls.

Limits

See also hazmat, rod, scram and security.