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 withbashInteractiveandcoreutils. 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 NAMEwithout-nhas the network again. Without-nthe 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
- Takes a scram frame labelled
pre-containerwhenscramis on thePATHandROD_INNERis not set. A failed frame is ignored. - Makes
/rod/containers/NAME/homeand/rod/containers/NAME/etc. - For a container without a profile, puts
bashInteractiveandcoreutilsin front of the packages you gave. - Installs every package that has a recipe with its own
nix-envcall, then all the others in one call. The output of nix-env is not shown. - A container that already existed is kept as it was.
- Writes four files in the container
etc:passwdwith the single lineroot:x:0:0:root:/root:/rod/profile/bin/bash,groupwithroot:x:0:,hostnamewith the name andhostswith127.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
/nix | The 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, /lib | The 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. |
/etc | The container etc directory, writable, with the four files above. /etc/resolv.conf and /etc/ssl of the host are mounted over it read-only. |
/root | The container home directory, writable. This is $HOME and the working directory. |
/tmp, /proc, /dev | A new memory filesystem, a procfs of the new PID namespace and a minimal /dev. |
| Everything else | Not 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
/rootand/etcinside. /rod/recipes/NAME.nix- A recipe that replaces the package of the same name.
ROD_ROOT- Replaces
/rodfor the package index, the recipes and the container directory. The paths/rod/profileinside the container do not change, and the tools add$ROD_ROOT/profile/binto thePATH. ROD_INNER- When set, silo takes no frames. rod sets it for its own calls.
Limits
- A container shares the host network unless
-nis given. A service in it listens on the addresses of the host, and the firewall of the host applies, see blastdoor. - silo sets no cgroup limit on memory, processes or CPU.
- silo installs no seccomp filter and does not pass
--new-session. A program in the container shares the terminal of the caller, and the kernel hasLEGACY_TIOCSTI=y, so it can push input into that terminal withTIOCSTI. - When the command ends, the namespaces end with it.
- Containers and the host use the package index of the host, so a container cannot hold a different release of the package set.