NixOS Containers: Difference between revisions

Line 13: Line 13:
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.
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|3=networking.nat = {
networking.nat = {
   enable = true;
   enable = true;
   internalInterfaces = ["ve-+"];
   internalInterfaces = ["ve-+"];
Line 48: Line 47:
     system.stateVersion = "24.11";
     system.stateVersion = "24.11";
   };
   };
};
};|name=configuration.nix|lang=nix}}
</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>. The web service of the container will be available at http://192.168.100.11
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
Line 59: Line 57:
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.
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)'''
===== NAT (Network Address Translation) =====
 
{{File|3=networking.nat = {
<syntaxhighlight lang="nix">
  enable = true;
</syntaxhighlight>
  internalInterfaces = ["ve-+"];
 
  externalInterface = "ens3";
'''Bridge'''
  # Lazy IPv6 connectivity for the container
  enableIPv6 = true;
};|name=configuration.nix|lang=nix}}


===== Bridge =====
<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
networking = {
networking = {
Line 91: Line 92:
</syntaxhighlight>
</syntaxhighlight>


==== Without privateNetwork (simpler) ====
===== Without privateNetwork (simpler) =====
 
If the service can be accessed by changing its port, the private network is not needed necessarily. Be careful to not use occupied ports. This example runs an [[Actual]] server on port 3003. It can be accessed through the host at <code>http://localhost:3003</code>. Since <code>privateNetwork</code> is not defined, it defaults to <code>false</code>.
If the service can be accessed by changing its port, the private network is not needed necessarily. Be careful to not use occupied ports. This example runs an [[Actual]] server on port 3003. It can be accessed through the host at <code>http://localhost:3003</code>. Since <code>privateNetwork</code> is not defined, it defaults to <code>false</code>.


Line 141: Line 141:
== Tips and tricks ==
== Tips and tricks ==


==== Define and create nixos-container from a Flake file ====
=== 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">
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">
{
{
Line 173: Line 174:
</syntaxhighlight>
</syntaxhighlight>


==== Use agenix secrets in container ====
=== 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">
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, ... }:
{ agenix, ... }:
Line 203: Line 205:
</syntaxhighlight>
</syntaxhighlight>


==== Connect two nixos-containers with privateNetwork together with a network bridge ====
=== Bridge together two nixos-containers with privateNetwork enabled ===
'''Target:'''
'''Target:'''


Line 217: Line 219:
'''Configuration:'''
'''Configuration:'''


* Create and configure the bridge and the internet connection
Create and configure the bridge and the internet connection
{{File|3=# Give containers access to the internet
{{File|3=# Give containers access to the internet
networking.nat = {
networking.nat = {
   enable = true;
   enable = true;
   # Use "ve-*" when using nftables instead of iptables
   # Use "ve-*" when using nftables instead of iptables
   internalInterfaces = [ "br0" ]; # connect the bridge to the internet
   internalInterfaces = [ "br0" ]; # Connect the bridge to the internet
   externalInterface = "wlp5s0";  # the interface that connects to the internet
   externalInterface = "wlp5s0";  # Adjust according to your internet interface
   # Lazy IPv6 connectivity for the container
   # Lazy IPv6 connectivity for the container
   enableIPv6 = true;
   enableIPv6 = true;
Line 239: Line 241:
   netdevs = {
   netdevs = {
       # Create the bridge interface
       # Create the bridge interface
       # The <number>- prefix is because systemd writes different files per configuration at /etc/systemd/network
       # Each interface is stored as a seperate file under /etc/systemd/network by default
       # All configuration files are collectively sorted and processed in alphanumeric order,
       # <number>-name is required so that it is not overwritten by other configurations of the same name
      # regardless of the directories in which they live.
      # However, files with identical filenames replace each other.
       # It is recommended that each filename is prefixed with a number smaller than "70" (e.g. 10-eth0.network).
       # It is recommended that each filename is prefixed with a number smaller than "70" (e.g. 10-eth0.network).
       # https://www.freedesktop.org/software/systemd/man/latest/systemd.network.html
       # https://www.freedesktop.org/software/systemd/man/latest/systemd.netdev.html
       "20-br0" = {
       "20-br0" = {
         netdevConfig = {
         netdevConfig = {
           Kind = "bridge";
           Kind = "bridge";
           Name = "br0";
           Name = "br0";
           # Sets a pre-determined mac address, leave empty if you want the system to auto-assign a mac address to the bridge
           # Sets a pre-determined mac address
          # Leave empty if you want the system to auto-assign a mac address to the bridge
           # MACAddress = "10:00:00:00:00:01";
           # MACAddress = "10:00:00:00:00:01";
         };
         };
Line 255: Line 256:
   };
   };
   networks = {
   networks = {
      # Connect the bridge ports to the bridge
      # it was unnecessary
      # "30-testcontain1" = {
        # vb- probably refers to virtual bridge
        # adding a hostBridge to each container creates vb- interfaces
        # the container hostname (which unless you manually change it is the container name)
        # should be <=61 characters, otherwise the system cannot rebuild if you force it to create an interface on the host
        # the interfaces have altnames of max 61+3 length, 64 characters
        # "ve-<containerHostname>" is the interface altname you can use
        # matchConfig.Name = "vb-testcontainer1";
        # networkConfig.Bridge = "br0";
        # linkConfig.RequiredForOnline = "enslaved";
      # };
      # "30-testcontain2" = {
        # matchConfig.Name = "vb-testcontainer2";
        # networkConfig.Bridge = "br0";
        # linkConfig.RequiredForOnline = "enslaved";
      # };
     # Configure the bridge for its desired function
     # Configure the bridge for its desired function
     "40-br0" = {
     "40-br0" = {
       matchConfig.Name = "br0";
       matchConfig.Name = "br0";
       # the address of the bridge
       # The address of the bridge
       # /29 is the netmask, it creates 2^(32-29) = 8 subnets
       # /29 is the netmask, it creates 2^(32-29) = 8 subnets
       # of which 2 are reserved (first and last), Network Address and Broadcast Address
       # 2 are reserved (first and last), as Network Address and Broadcast Address
       # the bridge already takes up one subnet, so 3 addresses are already reserved
       # The bridge already takes up one subnet, so 3 addresses are already reserved
       # thus, to bridge 2 networks, you need a netmask of <=29
       # To bridge 2 networks, you need a netmask of <=29 for IPv4
       # https://www.calculator.net/ip-subnet-calculator.html?cclass=any&csubnet=29&cip=192.168.100.1&ctype=ipv4&x=Calculate
       # https://www.calculator.net/ip-subnet-calculator.html?cclass=any&csubnet=29&cip=192.168.100.1&ctype=ipv4&x=Calculate
       # 192.168.100.0 - 192.168.100.7
       # 192.168.100.0 - 192.168.100.7
Line 288: Line 271:
         "192.168.100.1/29"
         "192.168.100.1/29"
       ];
       ];
      # routingPolicyRules = [
        # custom rules
      # ];
      # unnecessary to configure
      # routes = [
        # {
          # Gateway = "0.0.0.0";
          # Table = "bridge";
        # }
        # {
          # Gateway = "::";
          # Table = "bridge";
        # }
      # ];
       # bridgeConfig = {};
       # bridgeConfig = {};
       # Disable address autoconfig when no IP configuration is required
       # Disable address autoconfig when no IP configuration is required
Line 312: Line 281:
   };
   };
};|name=configuration.nix|lang=nix}}
};|name=configuration.nix|lang=nix}}
* Create and configure <code>containerA</code>
 
Create and configure <code>containerA</code>
{{File|3=containers.containerA = {
{{File|3=containers.containerA = {
   autoStart = true;
   autoStart = true;
   privateNetwork = true;
   privateNetwork = true;
   hostBridge = "br0";
   hostBridge = "br0";
   # hostAddress = "192.168.100.1";  # not used when using hostBridge
   # hostAddress = "192.168.100.1";  # Not used when using hostBridge
   localAddress = "192.168.100.2/29"; # should have the netmask if hostBridge is used
   localAddress = "192.168.100.2/29"; # Should have the netmask if hostBridge is used
   config =
   config =
     { config, pkgs, lib, ... }:
     { config, pkgs, lib, ... }:
Line 333: Line 303:
        
        
       networking = {
       networking = {
        firewall = {
          enable = true;
          allowedTCPPorts = [
            # 80 unnecessary
          ];
        };
         # Use systemd-resolved inside the container
         # Use systemd-resolved inside the container
         # Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686
         # Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686
Line 347: Line 311:
     };
     };
};|name=configuration.nix|lang=nix}}
};|name=configuration.nix|lang=nix}}
* Create and configure <code>containerB</code>
 
Create and configure <code>containerB</code>
{{File|3=containers.containerB = {
{{File|3=containers.containerB = {
   autoStart = true;
   autoStart = true;
   privateNetwork = true;
   privateNetwork = true;
   hostBridge = "br0";
   hostBridge = "br0";
   # hostAddress = "192.168.100.1";  # not used when using hostBridge
   # hostAddress = "192.168.100.1";  # Not used when using hostBridge
   localAddress = "192.168.100.3/29"; # should have the netmask if hostBridge is used
   localAddress = "192.168.100.3/29"; # Should have the netmask if hostBridge is used
   config =
   config =
     { config, pkgs, lib, ... }:
     { config, pkgs, lib, ... }:
Line 373: Line 338:
        
        
       networking = {
       networking = {
         firewall = {
         firewall.allowedTCPPorts = [ 80 ];
          enable = true;
          allowedTCPPorts = [
            80
          ];
        };
         # Use systemd-resolved inside the container
         # Use systemd-resolved inside the container
         # Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686
         # Workaround for bug https://github.com/NixOS/nixpkgs/issues/162686
Line 395: Line 355:
== 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