Firefox in a container

Introduction

In this article, we will explore the process of running a graphical user interface application within a NixOS container and accessing the application using noVNC. First, let’s understand what a NixOS container is and the benefits of running applications within a container. We will also cover TigerVNC and noVNC, the tools we will be using for this setup.

Understanding NixOS Containers

NixOS containers are a declarative way of configuring systemd-nspawn containers. These provide a light-weight approach to virtualisation with full access to the nix store. The main benefit in this use case is isolation. Applications in containers are isolated from the host and other containers, providing a secure and independent execution environment. Although it is important to note that a privileged user (root) in a container can escape the container and become root on the host system.

TigerVNC and NoVNC

TigerVNC is a high-performance, platform-independent VNC implementation that allows users to access GUI applications remotely. It will provide a VNC server allowing access to window manager and applications running inside the NixOS container.

noVNC is a browser-based VNC client that eliminates the need for installing VNC software on the client machine. It will allow a users to access the tigerVNC server using a web browser.

Workflow of the Solution

Now that we have covered the basics, let’s outline the workflow of setting up a GUI application within a NixOS container using TigerVNC and noVNC:

  1. Declare a NixOS Container: Create a NixOS container, ensuring that it is configured for running GUI applications and networking. This should include a private network to isolate it from external/ non-host connections and ephemeral state meaning it adopts a stateless declarative nature, allowing for reproducibility and scalability.

  2. Create a User: Within the container, create a dedicated user that will run the GUI applications with no access to root.

  3. Start TigerVNC Server: Launch the TigerVNC server within the container, using OpenBox as the window manager. Openbox is a lightweight window manager that provides a simple and customizable user interface.

  4. Launch Firefox: Start the Firefox browser within the Openbox window manager. This step can be modified to launch other applications or even a full desktop environment, such as XFCE.

  5. Start noVNC Server: The container initiates the noVNC server, which establishes a connection with the Tiger VNC server.

  6. Forward the noVNC Port: By forwarding the noVNC port out of the container, users can access the noVNC server using a web browser. This allows them to interact with the GUI application running within the NixOS container.

Code Implementation

firefox-novnc.nix

# Define the required packages, libraries, and configurations
{pkgs, lib, config, ...}:
with lib;
with builtins;

# Create a shell script named "firefox-xinit" and assign it to the `firefox-xinit` variable.
# This will be used by xinit to manually start an Xorg display server with firefox running on the openbox window manager
let firefox-xinit = pkgs.writeShellScriptBin "firefox-xinit"
''
${pkgs.firefox}/bin/firefox &       # Launch Firefox
exec ${pkgs.openbox}/bin/openbox    # Launch Openbox window manager
'';

in {
  config = {

    # Configure network address translation (NAT) to allow the container private network subnet
    networking.nat = {
      enable = true;                    # Enable NAT
      internalInterfaces = ["ve-+"];    # Specify internal interfaces
      externalInterface = "ens3";       # Specify external interface
    };

    # Configure the container
    containers.firefox-vnc = {
      ephemeral = true;                  # Enable an ephemeral container 
                                         # set to false to allow for an imperative container that persists configuration changes made from within the container
      autoStart = true;                  # Auto-start the container
      privateNetwork = true;             # Use the private network
      hostAddress = "192.168.8.1";       # Specify host IP address
      localAddress = "192.168.8.10";     # Specify local IP address

      config = { config, pkgs, ... }: {

        system.stateVersion = "22.05";   # Specify the system state version

        networking.firewall.allowedTCPPorts = [ 6080 ]; # Open the firewall TCP port 6080 to allow noVNC access outside the container

        # Configure firefox user
        users.users = {
          firefox = {
            home = "/home/firefox";       # Specify user's home directory
            useDefaultShell = true;       # Use the default shell for the user
            isNormalUser = true;          # Specify user as a normal user
          };
        };

        # Configure tigerVNC service to use firefox-xinit on display :1 with no security
        systemd.services.tiger-vnc = {
          wantedBy = [ "multi-user.target" ];
          serviceConfig = {
            ExecStart = ''${pkgs.xorg.xinit}/bin/xinit ${firefox-xinit}/bin/firefox-xinit -- ${pkgs.tigervnc}/bin/Xvnc :1 SecurityTypes=None'';
            User = "firefox";
          };
        };

        # Configure noVNC service to connect to tigerVNC on port 5901 and use default server of localhost:6080
        systemd.services.no-vnc = {
          wantedBy = [ "multi-user.target" ];
          path = [ pkgs.ps pkgs.hostname ];
          serviceConfig = {
            ExecStart = ''${pkgs.novnc}/bin/novnc -vnc localhost:5901'';
            User = "firefox";
          };
        };

        # Install required packages
        environment.systemPackages = with pkgs; [
          openbox
          firefox
          firefox-xinit
          novnc
          tigervnc
          xorg.xinit
        ];
      };
    };
  };
}

The above file can be imported as module or added to an existing configuration.nix file.

The declarative approach means that the container gets upgraded along with your host system when you run nixos-rebuild.

Once built the container can be entered using:

$ sudo nixos-container root-login firefox-vnc 

Once in the container the VNC servers can be confirmed as running correctly with:

# systemctl status tiger-vnc
# systemctl status no-vnc

noVNC can be accessed from a browser on the host machince at:

http://192.168.8.10:6080/vnc.html?host=192.168.8.10&port=6080

Further information relating to container management can be found in the NixOS Manual.

Conclusion: What next?

While the provided code allows for the running of a GUI application within a NixOS container using TigerVNC and noVNC, there are further considerations to ensure security and expand functionality. Here are some suggested next steps:

  1. Enhancing Security: Currently access to the tigerVNC server requires no security, it is recommended to implement additional security measures. This can involve setting up tigerVNC security (setting SecurityTypes in the Xvnc command) or utilizing an authentication/ authorization server like authelia.

  2. Container Access via Reverse Proxy: Instead of directly accessing the container, you can use a reverse proxy, such as Nginx, HAProxy or Traefik, to improve access control, provide additional security layers, and allow access from outside the host machine.

  3. Exploring Other Applications: While Firefox was used as an example, you can run any other application within the NixOS container, or a full desktop environment such as XFCE. This can be achieved by adding the required packages or services and editing the xinit script.

In summary, NixOS containers provide a convient and easy setup for sandboxing applications. Combined with noVNC this can provide a useful solution for accessing those applications.

July 2023