diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix
index c10e2b162ccc..e1a91f7704e7 100644
--- a/nixos/modules/virtualisation/containers.nix
+++ b/nixos/modules/virtualisation/containers.nix
@@ -70,7 +70,7 @@ let
startScript = cfg:
''
mkdir -p -m 0755 "$root/etc" "$root/var/lib"
- mkdir -p -m 0700 "$root/var/lib/private" "$root/root" /run/containers
+ mkdir -p -m 0700 "$root/var/lib/private" "$root/root"
if ! [ -e "$root/etc/os-release" ]; then
touch "$root/etc/os-release"
fi
@@ -138,7 +138,7 @@ let
--bind-ro=/nix/var/nix/daemon-socket \
--bind="/nix/var/nix/profiles/per-container/$INSTANCE:/nix/var/nix/profiles" \
--bind="/nix/var/nix/gcroots/per-container/$INSTANCE:/nix/var/nix/gcroots" \
- --link-journal=try-guest \
+ ${optionalString (!cfg.ephemeral) "--link-journal=try-guest"} \
--setenv PRIVATE_NETWORK="$PRIVATE_NETWORK" \
--setenv HOST_BRIDGE="$HOST_BRIDGE" \
--setenv HOST_ADDRESS="$HOST_ADDRESS" \
@@ -147,6 +147,7 @@ let
--setenv LOCAL_ADDRESS6="$LOCAL_ADDRESS6" \
--setenv HOST_PORT="$HOST_PORT" \
--setenv PATH="$PATH" \
+ ${optionalString cfg.ephemeral "--ephemeral"} \
${if cfg.additionalCapabilities != null && cfg.additionalCapabilities != [] then
''--capability="${concatStringsSep " " cfg.additionalCapabilities}"'' else ""
} \
@@ -247,6 +248,8 @@ let
Type = "notify";
+ RuntimeDirectory = [ "containers" ] ++ lib.optional cfg.ephemeral "containers/%i";
+
# Note that on reboot, systemd-nspawn returns 133, so this
# unit will be restarted. On poweroff, it returns 0, so the
# unit won't be restarted.
@@ -419,6 +422,7 @@ let
{
extraVeths = {};
additionalCapabilities = [];
+ ephemeral = false;
allowedDevices = [];
hostAddress = null;
hostAddress6 = null;
@@ -511,6 +515,26 @@ in
information.
'';
};
+
+ ephemeral = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Runs container in ephemeral mode with the empty root filesystem at boot.
+ This way container will be bootstrapped from scratch on each boot
+ and will be cleaned up on shutdown leaving no traces behind.
+ Useful for completely stateless, reproducible containers.
+
+ Note that this option might require to do some adjustments to the container configuration,
+ e.g. you might want to set
+ systemd.network.networks.$interface.dhcpConfig.ClientIdentifier to "mac"
+ if you use macvlans option.
+ This way dhcp client identifier will be stable between the container restarts.
+
+ Note that the container journal will not be linked to the host if this option is enabled.
+ '';
+ };
+
enableTun = mkOption {
type = types.bool;
default = false;
@@ -659,13 +683,8 @@ in
unit = {
description = "Container '%i'";
- unitConfig.RequiresMountsFor = [ "/var/lib/containers/%i" ];
-
path = [ pkgs.iproute ];
- environment.INSTANCE = "%i";
- environment.root = "/var/lib/containers/%i";
-
preStart = preStartScript dummyConfig;
script = startScript dummyConfig;
@@ -708,6 +727,9 @@ in
script = startScript containerConfig;
postStart = postStartScript containerConfig;
serviceConfig = serviceDirectives containerConfig;
+ unitConfig.RequiresMountsFor = lib.optional (!containerConfig.ephemeral) "/var/lib/containers/%i";
+ environment.root = if containerConfig.ephemeral then "/run/containers/%i" else "/var/lib/containers/%i";
+ environment.INSTANCE = "%i";
} // (
if containerConfig.autoStart then
{
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index c24c8ae61a58..166816777f31 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -47,6 +47,7 @@ in
codimd = handleTest ./codimd.nix {};
colord = handleTest ./colord.nix {};
containers-bridge = handleTest ./containers-bridge.nix {};
+ containers-ephemeral = handleTest ./containers-ephemeral.nix {};
containers-extra_veth = handleTest ./containers-extra_veth.nix {};
containers-hosts = handleTest ./containers-hosts.nix {};
containers-imperative = handleTest ./containers-imperative.nix {};
diff --git a/nixos/tests/containers-ephemeral.nix b/nixos/tests/containers-ephemeral.nix
new file mode 100644
index 000000000000..1ef8717d9a0e
--- /dev/null
+++ b/nixos/tests/containers-ephemeral.nix
@@ -0,0 +1,56 @@
+# Test for NixOS' container support.
+
+import ./make-test.nix ({ pkgs, ...} : {
+ name = "containers-ephemeral";
+
+ machine = { pkgs, ... }: {
+ virtualisation.memorySize = 768;
+ virtualisation.writableStore = true;
+
+ containers.webserver = {
+ ephemeral = true;
+ privateNetwork = true;
+ hostAddress = "10.231.136.1";
+ localAddress = "10.231.136.2";
+ config = {
+ services.nginx = {
+ enable = true;
+ virtualHosts.localhost = {
+ root = (pkgs.runCommand "localhost" {} ''
+ mkdir "$out"
+ echo hello world > "$out/index.html"
+ '');
+ };
+ };
+ networking.firewall.allowedTCPPorts = [ 80 ];
+ };
+ };
+ };
+
+ testScript = ''
+ $machine->succeed("nixos-container list") =~ /webserver/ or die;
+
+ # Start the webserver container.
+ $machine->succeed("nixos-container start webserver");
+
+ # Check that container got its own root folder
+ $machine->succeed("ls /run/containers/webserver");
+
+ # Check that container persistent directory is not created
+ $machine->fail("ls /var/lib/containers/webserver");
+
+ # Since "start" returns after the container has reached
+ # multi-user.target, we should now be able to access it.
+ my $ip = $machine->succeed("nixos-container show-ip webserver");
+ chomp $ip;
+ $machine->succeed("ping -n -c1 $ip");
+ $machine->succeed("curl --fail http://$ip/ > /dev/null");
+
+ # Stop the container.
+ $machine->succeed("nixos-container stop webserver");
+ $machine->fail("curl --fail --connect-timeout 2 http://$ip/ > /dev/null");
+
+ # Check that container's root folder was removed
+ $machine->fail("ls /run/containers/webserver");
+ '';
+})