NixOS Containers: Difference between revisions
imported>Alyaeanyx m Add warning and improve formatting |
m Add command to view log of container |
||
(27 intermediate revisions by 14 users not shown) | |||
Line 1: | Line 1: | ||
Setup native [https://wiki.archlinux.org/title/systemd-nspawn systemd-nspawn] containers, which are running NixOS and are configured and managed by NixOS using the <code>containers</code> directive. | |||
See [[Docker]] page for OCI container (Docker, Podman) configuration. | |||
The following example creates a container called | === Configuration === | ||
The following example creates a container called webserver running a httpd web server. It will start automatically at boot and has its private network subnet. | |||
{{file|/etc/nixos/configuration.nix|nix|<nowiki> | {{file|/etc/nixos/configuration.nix|nix|<nowiki> | ||
Line 10: | Line 12: | ||
internalInterfaces = ["ve-+"]; | internalInterfaces = ["ve-+"]; | ||
externalInterface = "ens3"; | externalInterface = "ens3"; | ||
# Lazy IPv6 connectivity for the container | |||
enableIPv6 = true; | |||
}; | }; | ||
containers. | containers.webserver = { | ||
autoStart = true; | autoStart = true; | ||
privateNetwork = true; | privateNetwork = true; | ||
hostAddress = "192.168.100.10"; | hostAddress = "192.168.100.10"; | ||
localAddress = "192.168.100.11"; | localAddress = "192.168.100.11"; | ||
config = { config, pkgs, ... }: { | hostAddress6 = "fc00::1"; | ||
localAddress6 = "fc00::2"; | |||
config = { config, pkgs, lib, ... }: { | |||
services. | services.httpd = { | ||
enable = true; | enable = true; | ||
adminAddr = "admin@example.org"; | |||
}; | }; | ||
networking = { | |||
firewall.allowedTCPPorts = [ 80 ]; | |||
# Use systemd-resolved inside the container | |||
# Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686 | |||
useHostResolvConf = lib.mkForce false; | |||
}; | }; | ||
services.resolved.enable = true; | |||
system.stateVersion = "24.11"; | |||
}; | }; | ||
}; | }; | ||
</nowiki>}} | </nowiki>}} | ||
In order to reach the web application on the host system, we have to open [[Firewall]] port 80 and also configure NAT through <code>networking.nat</code>. | In order to reach the web application on the host system, we have to open [[Firewall]] port 80 and also configure NAT through <code>networking.nat</code>. The web service of the container will be available at http://192.168.100.11 | ||
==== Networking ==== | |||
{{expansion}} | |||
By default, if <code>privateNetwork</code> is not set, the container shares the network with the host, enabling it to bind any port on any interface. However, when <code>privateNetwork</code> is set to <code>true</code>, the container gains its private virtual <code>eth0</code> and <code>ve-<container_name></code> on the host. This isolation is beneficial when you want the container to have its dedicated networking stack. | |||
'''NAT (Network Address Translation)''' | |||
<syntaxhighlight lang="nix"> | |||
</syntaxhighlight> | |||
'''Bridge''' | |||
<syntaxhighlight lang="nix"> | |||
networking = { | |||
bridges.br0.interfaces = [ "eth0s31f6" ]; # Adjust interface accordingly | |||
# Get bridge-ip with DHCP | |||
useDHCP = false; | |||
interfaces."br0".useDHCP = true; | |||
# Set bridge-ip static | |||
interfaces."br0".ipv4.addresses = [{ | |||
address = "192.168.100.3"; | |||
prefixLength = 24; | |||
}]; | |||
defaultGateway = "192.168.100.1"; | |||
nameservers = [ "192.168.100.1" ]; | |||
}; | |||
containers.<name> = { | |||
privateNetwork = true; | |||
hostBridge = "br0"; # Specify the bridge name | |||
localAddress = "192.168.100.5/24"; | |||
config = { }; | |||
}; | |||
</syntaxhighlight> | |||
=== Usage === | |||
List containers | |||
<syntaxhighlight lang="console"> | |||
# machinectl list | |||
</syntaxhighlight> | |||
Checking the status of the container | Checking the status of the container | ||
<syntaxhighlight lang="console"> | <syntaxhighlight lang="console"> | ||
# systemctl status container@ | # systemctl status container@webserver | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Login into the container | Login into the container | ||
<syntaxhighlight lang="console"> | <syntaxhighlight lang="console"> | ||
# nixos-container root-login | # nixos-container root-login webserver | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Start or stop a container | Start or stop a container | ||
<syntaxhighlight lang="console"> | <syntaxhighlight lang="console"> | ||
# nixos-container start | # nixos-container start webserver | ||
# nixos-container stop | # nixos-container stop webserver | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Destroy a container including its file system | Destroy a container including its file system | ||
<syntaxhighlight lang="console"> | <syntaxhighlight lang="console"> | ||
# nixos-container destroy | # nixos-container destroy webserver | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Further informations are available in the {{manual:nixos|sec=#ch-containers|chapter=NixOS manual}}. | View log for container<syntaxhighlight lang="console"> | ||
# journalctl -M webserver | |||
</syntaxhighlight>Further informations are available in the {{manual:nixos|sec=#ch-containers|chapter=NixOS manual}}. | |||
== Tips and tricks == | |||
== | ==== Define and create nixos-container from a Flake file ==== | ||
We can define and create a custom container called <code>container</code> from a file stored as <code>flake.nix</code>. In this case we use the unstable branch of the nixpkgs repository as a source.<syntaxhighlight lang="nix"> | |||
{ | |||
inputs.nixpkgs.url = "nixpkgs/nixos-unstable"; | |||
outputs = { self, nixpkgs }: { | |||
{ | |||
nixosConfigurations.container = nixpkgs.lib.nixosSystem { | |||
system = "x86_64-linux"; | |||
modules = | |||
[ ({ pkgs, ... }: { | |||
boot.isContainer = true; | |||
networking.firewall.allowedTCPPorts = [ 80 ]; | |||
services.httpd = { | |||
enable = true; | |||
adminAddr = "morty@example.org"; | |||
}; | |||
}) | |||
]; | ]; | ||
}; | |||
}; | |||
} | |||
</syntaxhighlight>To create and run that container, enter following commands. In this example the <code>flake.nix</code> file is in the same directory.<syntaxhighlight lang="console"> | |||
# nixos-container create flake-test --flake . | |||
host IP is 10.233.4.1, container IP is 10.233.4.2 | |||
# nixos-container start flake-test | |||
</syntaxhighlight> | |||
==== Use agenix secrets in container ==== | |||
To add <code>agenix</code> secrets to a container bind mount the <code>ssh-host.key</code> and import the <code>agenix.nixosModule</code> and set <code>age.identityPaths</code> [https://discourse.nixos.org/t/secrets-inside-nixos-containers/34403/6 Source]<syntaxhighlight lang="nix"> | |||
{ agenix, ... }: | |||
{ | |||
containers."withSecret" = { | |||
# pass the private key to the container for agenix to decrypt the secret | |||
bindMounts."/etc/ssh/ssh_host_ed25519_key".isReadOnly = true; | |||
config = | |||
{ | |||
config, | |||
lib, | |||
pkgs, | |||
... | |||
}: | |||
{ | |||
imports = [ agenix.nixosModules.default ]; # import agenix-module into the nixos-container | |||
age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; # isn't set automatically when openssh is not setup | |||
# import the secret | |||
age.secrets."secret-name" = { | |||
file = ../secrets/secret.age; | |||
}; | |||
}; | }; | ||
}; | |||
} | |||
</syntaxhighlight> | |||
== Troubleshooting == | == Troubleshooting == | ||
=== I have changed the host's channel and some services are no longer functional === | ==== I have changed the host's channel and some services are no longer functional ==== | ||
'''Symptoms:''' | '''Symptoms:''' | ||
* Lost data in PostgreSQL database | * Lost data in PostgreSQL database | ||
Line 101: | Line 199: | ||
* [https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/virtualization/nixos-container/nixos-container.pl Nixpkgs - nixos-container.pl] | * [https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/virtualization/nixos-container/nixos-container.pl Nixpkgs - nixos-container.pl] | ||
* [https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/nixos-containers.nix Nixpkgs - nixos-containers.nix] | * [https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualisation/nixos-containers.nix Nixpkgs - nixos-containers.nix] | ||
* [https://nixcademy.com/2023/08/29/nixos-nspawn/ nixos-nspawn] | |||
* [https://github.com/tfc/nspawn-nixos tfc/nspawn-nixos] | |||
* MicroVMs as a more isolated alternative, e.g. with https://github.com/astro/microvm.nix | |||
[[Category:Server]] | [[Category:Server]] | ||
[[Category:NixOS]] | [[Category:NixOS]] | ||
[[Category:Container]] |
Revision as of 00:08, 25 June 2025
Setup native systemd-nspawn containers, which are running NixOS and are configured and managed by NixOS using the containers
directive.
See Docker page for OCI container (Docker, Podman) configuration.
Configuration
The following example creates a container called webserver running a httpd web server. It will start automatically at boot and has its private network subnet.
networking.nat = {
enable = true;
internalInterfaces = ["ve-+"];
externalInterface = "ens3";
# Lazy IPv6 connectivity for the container
enableIPv6 = true;
};
containers.webserver = {
autoStart = true;
privateNetwork = true;
hostAddress = "192.168.100.10";
localAddress = "192.168.100.11";
hostAddress6 = "fc00::1";
localAddress6 = "fc00::2";
config = { config, pkgs, lib, ... }: {
services.httpd = {
enable = true;
adminAddr = "admin@example.org";
};
networking = {
firewall.allowedTCPPorts = [ 80 ];
# Use systemd-resolved inside the container
# Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686
useHostResolvConf = lib.mkForce false;
};
services.resolved.enable = true;
system.stateVersion = "24.11";
};
};
In order to reach the web application on the host system, we have to open Firewall port 80 and also configure NAT through networking.nat
. The web service of the container will be available at http://192.168.100.11
Networking
By default, if privateNetwork
is not set, the container shares the network with the host, enabling it to bind any port on any interface. However, when privateNetwork
is set to true
, the container gains its private virtual eth0
and ve-<container_name>
on the host. This isolation is beneficial when you want the container to have its dedicated networking stack.
NAT (Network Address Translation)
Bridge
networking = {
bridges.br0.interfaces = [ "eth0s31f6" ]; # Adjust interface accordingly
# Get bridge-ip with DHCP
useDHCP = false;
interfaces."br0".useDHCP = true;
# Set bridge-ip static
interfaces."br0".ipv4.addresses = [{
address = "192.168.100.3";
prefixLength = 24;
}];
defaultGateway = "192.168.100.1";
nameservers = [ "192.168.100.1" ];
};
containers.<name> = {
privateNetwork = true;
hostBridge = "br0"; # Specify the bridge name
localAddress = "192.168.100.5/24";
config = { };
};
Usage
List containers
# machinectl list
Checking the status of the container
# systemctl status container@webserver
Login into the container
# nixos-container root-login webserver
Start or stop a container
# nixos-container start webserver
# nixos-container stop webserver
Destroy a container including its file system
# nixos-container destroy webserver
View log for container
# journalctl -M webserver
Further informations are available in the NixOS Manual, NixOS manual.
Tips and tricks
Define and create nixos-container from a Flake file
We can define and create a custom container called container
from a file stored as flake.nix
. In this case we use the unstable branch of the nixpkgs repository as a source.
{
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
outputs = { self, nixpkgs }: {
nixosConfigurations.container = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules =
[ ({ pkgs, ... }: {
boot.isContainer = true;
networking.firewall.allowedTCPPorts = [ 80 ];
services.httpd = {
enable = true;
adminAddr = "morty@example.org";
};
})
];
};
};
}
To create and run that container, enter following commands. In this example the flake.nix
file is in the same directory.
# nixos-container create flake-test --flake .
host IP is 10.233.4.1, container IP is 10.233.4.2
# nixos-container start flake-test
Use agenix secrets in container
To add agenix
secrets to a container bind mount the ssh-host.key
and import the agenix.nixosModule
and set age.identityPaths
Source
{ agenix, ... }:
{
containers."withSecret" = {
# pass the private key to the container for agenix to decrypt the secret
bindMounts."/etc/ssh/ssh_host_ed25519_key".isReadOnly = true;
config =
{
config,
lib,
pkgs,
...
}:
{
imports = [ agenix.nixosModules.default ]; # import agenix-module into the nixos-container
age.identityPaths = [ "/etc/ssh/ssh_host_ed25519_key" ]; # isn't set automatically when openssh is not setup
# import the secret
age.secrets."secret-name" = {
file = ../secrets/secret.age;
};
};
};
}
Troubleshooting
I have changed the host's channel and some services are no longer functional
Symptoms:
- Lost data in PostgreSQL database
- MySQL has changed its path, where it creates the database
Solution
If you did not have a system.stateVersion
option set inside your declarative container configuration, it will use the default one for the channel. Your data might be safe, if you did nothing meanwhile. Add the missing system.stateVersion
to your container, rebuild, and possibly stop/start the container.
See also
- NixOS Manual, Chapter on Container Management
- Blog Article - Declarative NixOS Containers
- NixOS Discourse - Extra-container: Run declarative containers without full system rebuilds
- Nixpkgs - nixos-container.pl
- Nixpkgs - nixos-containers.nix
- nixos-nspawn
- tfc/nspawn-nixos
- MicroVMs as a more isolated alternative, e.g. with https://github.com/astro/microvm.nix