# extra-container Manage declarative NixOS containers like imperative containers, without system rebuilds. Each declarative container adds a full system module evaluation to every NixOS rebuild, which can be prohibitively slow for systems with many containers or when experimenting with single containers. On the other hand, the faster imperative containers lack the full range of options of declarative containers. This tool brings you the best of both worlds. ## Example ```bash sudo extra-container create --start <<'EOF' { containers.demo = { privateNetwork = true; hostAddress = "10.250.0.1"; localAddress = "10.250.0.2"; config = { pkgs, ... }: { systemd.services.hello = { wantedBy = [ "multi-user.target" ]; script = '' while true; do echo hello | ${pkgs.netcat}/bin/nc -lN 50 done ''; }; networking.firewall.allowedTCPPorts = [ 50 ]; }; }; } EOF curl --http0.9 10.250.0.2:50 # Returns 'hello' from the container # Now change the 'hello' string in the container definition to something # else and re-run the `extra-container create --start` command. # The container is automatically updated via NixOS' `switch-to-configuration`. # The container is a regular container that can be controlled # with nixos-container nixos-container status demo # Remove the container sudo extra-container destroy demo ``` #### Run command in a container and exit ```bash cfg='{ containers.demo.config = { networking.hostName = "hello"; }; }' extra-container shell -E "$cfg" --run c hostname # => hello ``` ## Changelog [`CHANGELOG.md`](CHANGELOG.md) ## Install ### On NixOS ##### NixOS ≥ 21.11 Add `programs.extra-container.enable = true` to your configuration. ##### Any NixOS with `flake` support Import `extra-container.nixosModules.default` in your configuration. ### On other systemd-based Linux distros ```bash git clone https://github.com/erikarvstedt/extra-container # Calls sudo during install extra-container/util/install.sh ``` [`install.sh`](util/install.sh) installs `extra-container` to the root nix user profile and edits `/etc/sudoers` to enable running `extra-container` with sudo. ## More features ### Shell Command `shell` starts a container shell session. The shell provides helper functions for interacting with the container. The container is destroyed when exiting the shell. This config uses `extra` options that are [explained below](#private-network-helper). ```bash read -d '' src <<'EOF' || : { containers.demo = { extra.addressPrefix = "10.250.0"; # Sets up a private network. extra.enableWAN = true; }; } EOF # Provide container config via `-E` instead of stdin because the shell's stdin # should be connected to the terminal. extra-container shell -E "$src" --ssh ``` `extra-container` automatically runs itself via `sudo` when called as a non-root user. An example shell session ``` ... Starting shell. Enter "h" for documentation. $ h Container address: 10.250.0.2 ($ip) Container filesystem: /var/lib/nixos-containers/demo Run "c COMMAND" to execute a command in the container Run "c" to start a shell session inside the container Run "cssh" for SSH # Container internet access, enabled via option `extra.enableWAN` $ c curl example.com ... # Connect with SSH, enabled by `--ssh` $ cssh hostname demo ``` #### Run commands Run a command in a shell session and exit. The container is destroyed afterwards. ```bash cfg='{ containers.demo = {}; }' extra-container shell -E "$cfg" --run c hostname # => demo ``` Start a shell inside the container. ```bash cfg='{ containers.demo = {}; }' extra-container shell -E "$cfg" --run c ``` #### Repeated calls to `extra-container shell` When `extra-container shell` detects that it is already running in a container shell session, it updates the running container instead of destroying and restarting it and starting a new shell.\ To prevent `sudo` from clearing the environment variables that are needed for shell detection, call `extra-container` without `sudo`.\ `extra-container` will automatically run itself via `sudo` only when it is first called as a non-root user outside of a shell session. To force container destruction inside a shell session, use `extra-container shell --destroy|-d`. #### Disable auto-destruction By default, `shell` destroys the shell container before starting and before exiting. This ensures that containers start with no leftover filesystem state from previous runs and that containers do not consume system resources after use.\ To disable auto-destructing containers, run `extra-container shell --no-destroy|-n` ### Private network helper Container options `extra.*` are defined by `extra-container` and help with setting up private network containers.\ See [eval-config.nix](./eval-config.nix) for full option descriptions. ```nix containers.demo = { extra = { # Sets # privateNetwork = true # hostAddress = "${addressPrefix}.1" # localAddress = "${addressPrefix}.2" addressPrefix = "10.250.0"; # Enable internet access for the container enableWAN = true; # Always allow connections from hostAddress firewallAllowHost = true; # Make the container's localhost reachable via localAddress exposeLocalhost = true; } }; ``` ### Access working dir in non-file configs `extra-container` appends `pwd` to `NIX_PATH` to allow configs given via `--expr|-E` or via stdin to access the working directory. ```bash extra-container create -E '{ imports = [ ]; ... }' ``` ## Define containers via Flakes See [examples/flake](./examples/flake). ## Usage ``` extra-container create [--attr|-A attrPath] [--nixpkgs-path|--nixos-path path] [--start|-s | --restart-changed|-r] [--ssh] [--build-args arg...] is a NixOS config file with container definitions like 'containers.mycontainer = { ... }' --attr | -A attrPath Select an attribute from the config expression --nixpkgs-path A nix expression that returns a path to the nixpkgs source to use for building the containers --nixos-path Like '--nixpkgs-path', but for directly specifying the NixOS source --start | -s Start all created containers Update running containers that have changed or restart them if '--restart-changed' was specified --update-changed | -u Update running containers with a changed system configuration by running 'switch-to-configuration' inside the container. Restart containers with a changed container configuration --restart-changed | -r Restart running containers that have changed --ssh Generate SSH keys in /tmp and enable container SSH access. The key files remain after exit and are reused on subsequent runs. Unlocks the function 'cssh' in 'extra-container shell'. Requires container option 'privateNetwork = true'. --build-args arg... All following args are passed to nix-build. Example: extra-container create mycontainers.nix --restart-changed extra-container create mycontainers.nix --nixpkgs-path \ 'fetchTarball https://nixos.org/channels/nixos-unstable/nixexprs.tar.xz' extra-container create mycontainers.nix --start --build-args --builders 'ssh://worker - - 8' echo | extra-container create Read the container config from stdin Example: extra-container create --start < Provide container config as an argument extra-container create Create containers from /etc Examples: Create from nixos system derivation extra-container create /nix/store/9h..27-nixos-system-foo-18.03 Create from nixos etc derivation extra-container create /nix/store/32..9j-etc extra-container shell ... Start a container shell session. See the README for a complete documentation. Supports all arguments from 'create' Extra arguments: --run ... Run command in shell session and exit Must be the last option given --no-destroy|-n Do not destroy shell container before and after running --destroy|-d If running inside an existing shell session, force container to be destroyed before and after running Example: extra-container shell -E '{ containers.demo.config = {}; }' extra-container build ... Build the container config and print the resulting NixOS system etc path This command can be used like 'create', but options related to starting are not supported extra-container list List all extra containers extra-container restart ... Fixes the broken restart command of nixos-container (nixpkgs issue #43652) extra-container destroy ... Destroy containers extra-container destroy ... Destroy the containers defined by the args for command `create` or `shell` (see above). For this to work, the first arg after `destroy` must start with one of the following three characters: ./- Example: extra-container destroy ./containers.nix extra-container destroy --all|-a Destroy all extra containers extra-container ... All other commands are forwarded to nixos-container ``` ## Implementation The script works like this: Take a NixOS config with container definitions and build the system's `config.system.build.etc` derivation. Because we're not building a full system we can use a reduced module set (`eval-config.nix`) to improve evaluation performance. Now link the container files from the etc derivation to the main system, like so: ``` nixos-system/etc/systemd/system/container@CONTAINER.service -> /etc/systemd-mutable/system nixos-system/etc/containers/CONTAINER.conf -> /etc/containers (system.stateVersion < 22.05) -> /etc/nixos-containers (system.stateVersion ≥ 22.05) ``` Finally, add gcroots pointing to the linked files. ## Developing All contributions and suggestions are welcome, even if they're minor or cosmetic. ### Development workflow Run `nix develop` in the project root directory to start a development shell.\ Within the shell, you can run extra-container from the [local source](./extra-container) via command `extra-container`. When changing the `Usage` documentation in `extra-container`, run `make doc` to copy these changes to `README.md`. #### Tests Run `make` to run the tests.\ The following tests are executed: - Main test suite Can be run manually via `sudo run-tests-in-container.sh`.\ This script creates a temporary container named `test-extra-container` in which the main test script [`test.sh`](./test.sh) is run.\ `test.sh` adds and removes temporary containers named `test-*` on the host system. It can also be called directly, but wrapping it with `run-tests-in-container.sh` helps reducing interference with your main system. - VM test Can be run manually via `nix build .#test`.\ This is a basic test using the [NixOS VM test framework](https://github.com/NixOS/nixpkgs/blob/master/nixos/lib/testing-python.nix). It is built as a Nix derivation, which makes it independent from the system environment. For debugging the VM, run `nix run .#debugTest` to start a Python test driver shell inside the VM. - `nix flake check` Evaluates all flake outputs and builds the VM test. #### VM Run `nix run .#vm` to start a VM where `extra-container` is installed.\ This provides an isolated and reproducible testing environment.