allows us to configure regular linux distros System Manager

Declarative system management for any Linux machine using Nix

Skip to content
System Manager
main
Getting Started


 
logo System Manager
 numtide/system-manager
v1.1.0
1.5k
50
Home
Tutorials
Tutorials
Getting Started
Your First Service
Multi-file Configuration
How-to Guides
How-to Guides
Installation
Deploy Remotely
Use Remote Flakes
Use Blueprint
Import a NixOS Module
Test Configuration
Manage Existing Files
Rollback Changes
Reference
Reference
CLI Commands
Module Options
All Options
Configuration Patterns
Supported Platforms
Explanation
Explanation
What is System Manager?
Declarative Configuration
How It Works
NixOS Comparison
Examples
Examples
Timer
Docker
PostgreSQL
Nginx
Nginx HTTPS
Custom App
FAQ
Home
Tutorials
Getting Started¶

If you've heard of NixOS, you've probably heard that it lets you define your entire system in configuration files and then reproduce that system anywhere with a single command. System Manager brings that same declarative model to other Linux distributions*, with no reinstalling, no switching operating systems, and no special prerequisites beyond having Nix installed.

*Presently, System Manager is only tested on Ubuntu, and is limited to only Linux distributions based on systemd.

Initializing Your System¶

Before proceeding, ensure you have installed Nix with flakes enabled.

To get started with System Manager, run the init subcommand, which will create an initial set of files in the ~/.config/system-manager folder:


nix run 'github:numtide/system-manager' -- init
(Remember, the double dash -- signifies that any options following it are passed to the following command, in this case System Manager, rather than to the main command, nix).

You might see the following for questions; you can simply answer yes to them:

Do you want to allow configuration setting 'extra-substituters' to be set to 'https://cache.numtide.com' (y/N)?

Do you want to permanently mark this value as trusted (y/N)?

Do you want to allow configuration setting 'extra-trusted-public-keys' to be set to 'niks3.numtide.com-1:DTx8wZduET09hRmMtKdQDxNNthLQETkc/yaX7M4qK0g=' (y/N)?

Do you want to permanently mark this value as trusted (y/N)?

After running the command, you will have the following files in your ~/.config/system-manager folder:

flake.nix -- A flake entrypoint that loads the system.nix file
system.nix -- The declarative file that describes what your system should look like.
Tip
Because this is your first time running System Manager, Nix will download and build several files, which might take some time. This only happens once, and in the future, System Manager will run very quickly.

Here are the contents of the files that were created:

flake.nix¶


{
  description = "Standalone System Manager configuration";

  inputs = {
    # Specify the source of System Manager and Nixpkgs.
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    system-manager = {
      url = "github:numtide/system-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs =
    {
      self,
      nixpkgs,
      system-manager,
      ...
    }:
    let
      system = "x86_64-linux";
    in
    {
      systemConfigs.default = system-manager.lib.makeSystemConfig {
        # Specify your system configuration modules here, for example,
        # the path to your system.nix.
        modules = [ 
            ./system.nix 
        ];

        # Optionally specify extraSpecialArgs and overlays
      };
    };
}
system.nix¶


{ lib, pkgs, ... }:
{
  config = {
    nixpkgs.hostPlatform = "x86_64-linux";

    # Enable and configure services
    services = {
      # nginx.enable = true;
    };

    environment = {
      # Packages that should be installed on a system
      systemPackages = [
        # pkgs.hello
      ];

      # Add directories and files to `/etc` and set their permissions
      etc = {
        # with_ownership = {
        #   text = ''
        #     This is just a test!
        #   '';
        #   mode = "0755";
        #   uid = 5;
        #   gid = 6;
        # };
        #
        # with_ownership2 = {
        #   text = ''
        #     This is just a test!
        #   '';
        #   mode = "0755";
        #   user = "nobody";
        #   group = "users";
        # };
      };
    };

    # Enable and configure systemd services
    systemd.services = { };

    # Configure systemd tmpfile settings
    systemd.tmpfiles = {
      # rules = [
      #   "D /var/tmp/system-manager 0755 root root -"
      # ];
      #
      # settings.sample = {
      #   "/var/tmp/sample".d = {
      #     mode = "0755";
      #   };
      # };
    };
  };
}
Example: Installing/Uninstalling Apps¶

First, let's create a configuration file that allows for the installation or uninstallation of apps.

Tip
The idea is that the configuration file describes what the system should look like. Keep that in mind, rather than thinking that the configuration file "installs software" or "uninstalls software."

To get started, we'll create another .nix file that will install a single app. Then we'll run System Manager and verify it's installed.

Then, to demonstrate what System Manager can do, we'll add another line to the configuration file with another app; run System Manager again, and verify its installation once more.

Then, after that, we'll remove one of the apps from the configuration file, run System Manager, and verify that the app is no longer installed.

This will fully demonstrate the declarative nature of these configuration files.

First, in the ~/.config/system-manager folder, create a file apps.nix and place the following in it:


{ pkgs, ... }:
{
  nixpkgs.hostPlatform = "x86_64-linux";

  environment.systemPackages = with pkgs; [
    tldr
  ];
}
This configuration states that the system being configured should have the tldr app present, and if it isn't, System Manager will install it. (Notice how we phrased that! We didn't just say this file installs the app. With .nix files, it's important to get into the mindset that they state what the system should look like.)

Now add the file to the modules list in flake.nix by replacing this modules line:


-         modules = [ ./system.nix ];
+         modules = [
+             ./system.nix
+             ./apps.nix
+         ];
Note: By default, system.nix includes starter code and some commented-out examples, and nothing else. You can leave it in the list; in its original state, it does nothing.

Next, we'll run System Manager to apply the configuration.

System Manager requires root privileges to modify /etc, manage systemd services, and create system profiles. Use the --sudo flag to run these operations via sudo:


nix run 'github:numtide/system-manager' -- switch --flake . --sudo
After a short moment, the tldr app should be installed on your system.

Tip
The first time you install software with System Manager, it adds a path to your $PATH variable by creating an entry in /etc/profile.d/. This won't take effect until you log out and back in; or you can source the file like so: source /etc/profile.d/system-manager-path.sh After that, you should find the tldr program: which tldr should yield /run/system-manager/sw/bin//tldr.

Now, to demonstrate the declarative feature of System Manager, let's add another app to the list. Here's a fun app called cowsay. Add a single line cowsay to the list passed into systemPackages:


{ pkgs, ... }:
{
  config = {
    nixpkgs.hostPlatform = "x86_64-linux";

    environment.systemPackages = with pkgs; [
      tldr
      cowsay
    ];
  };
}
Run System Manager again with the same command as above, and you should now have cowsay on your system:


~/.config/system-manager$ cowsay Hello!
 ________
< Hello! >
 --------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
~/.config/system-manager$
Remember, this is a declarative approach; System Manager did not reinstall tldr. It looked at the list (tldr, cowsay) and compared it to what is currently installed. It saw that tldr is already installed, so it skipped that one. It saw cowsay is not installed, so it installed it, so that the system matches the configuration file.

Now let's remove cowsay from the list of installed software. To do so, simply remove the line (or comment it out):


{ pkgs, ... }:
{
  config = {
    nixpkgs.hostPlatform = "x86_64-linux";

    environment.systemPackages = with pkgs; [
      tldr
    ];
  };
}
Notice this file now looks exactly as it did before adding in cowsay, meaning the system will now look like it did before adding in cowsay. Re-run System Manager and you'll see that cowsay is no longer installed.

Understanding the config attribute set¶

In the above example, we added attributes to the systemPackages set, which is a part of the environment attribute set, which in turn is part of the config attribute set.

When System Manager runs, with the help of the Nix language, you can have multiple config attribute sets, and System Manager combines them into a single attribute set. This allows you to have different setups in separate files, and simply combine them side by side, only having to add on to this line:


        modules = [
            ./system.nix
            ./apps.nix
        ];
However, you need to be careful. Suppose you have a different set of software you want to install, and you create a flake in another area in the filesystem with that software. It may not work the way you intend.

With that second flake, System Manager will gather up any apps you have in the systemPackages attribute, and compare that to what it has already installed earlier. If the packages installed earlier aren't included, it will remove those packages. (That includes the apps you installed with the "other" flake.)

In other words, you cannot have two separate flakes, one for one set of software, the other for a different set of software, and bounce between those flakes. System Manager will treat the second as requesting to uninstall what it had previously installed.

To make the above work, your best bet is to create a single flake and add in individual files that contain the apps you want to install, and always run from that same location.

Concepts for people new to Nix¶

Understanding Imperative State vs Declarative State¶

Imperative state means you change the system by hand, step by step. You run commands like apt install, edit files under /etc with a text editor, toggle systemd services, and make changes as you think of them. You're telling the computer how to do something:

"Install this package, then edit this file, then restart this service."

Each action mutates the system in place, and over time, the machine can drift into a state that's hard to reproduce.

(To mutate something simply means to change it in place. When a system mutates, its files, settings, or state are altered directly, step by step, rather than being reconstructed from a clean, known description.)

Declarative state, on the other hand, means you don't tell the system how to do the steps — you tell it what you want the final system to look like, and the tool (System Manager, NixOS, Home Manager, etc.) figures out the steps automatically.

"This machine should have these packages, these /etc files, and these services enabled." When you activate that configuration, the tool builds the desired end state and applies it in a predictable and repeatable manner.

Here's A simple analogy:

Imperative is like writing a recipe with every individual action:

"Chop onions. Heat the pan. Add oil..."

Declarative is like saying:

"I want a finished lasagna"

and the system knows how to assemble it reliably every time.

Declarative state avoids drift, keeps everything versioned and reproducible, and makes rollbacks simple. Imperative state is flexible and quick, but much harder to track or repeat.

Traditional programming languages are typically imperative in nature.

If you're familiar with coding, a language like JavaScript is imperative in that you describe everything in a step-by-step fashion. A language like HTML is declarative, in that you simply state what the web page should look like, without specifying how to achieve it.

A note about objects in your .nix files¶

Nix gives you significant flexibility in creating your own objects that you use inside a *.nix file.

For example, you could have a config object that looks like this:


config = {
  nixpkgs = {
    hostPlatform = "x86_64-linux";
  }
}
This declares an object stored as config with a single member called nixpkgs; that nixpkgs member then has a single member called hostPlatform, holding the string literal "x86_64-linux".

But Nix allows great flexibility in how you declare such objects. Consider the following:


  config = {
    nixpkgs.hostPlatform = "x86_64-linux";
  }
This creates the exact same object. Nix allows you to string together members with a dot between them, and it will construct the inner object accordingly.
Note
In the examples throughout this and other guides, we use a combination of the above syntax.

January 27, 2026
 
May 19, 2025
Previous
Home
Next
Your First Service
Content on this site is licensed under a Creative Commons Attribution-ShareAlike 4.0 International license.

Skip to content
logo
System Manager
main
Your First Service


  
 numtide/system-manager
v1.1.0
1.5k
50
Home
Tutorials
How-to Guides
Reference
Explanation
Examples
FAQ
Tutorials
Getting Started
Your First Service
Multi-file Configuration
Table of contents
Prerequisites
What You'll Build
Step 1: Create a Service Module
Step 2: Add the Module to Your Flake
Step 3: Apply the Configuration
Step 4: Verify It Worked
Step 5: Check the Generated Service File
Next Steps
Home
Tutorials
Your First Systemd Service¶

This tutorial walks you through creating and managing a systemd service with System Manager.

Prerequisites¶

System Manager initialized (see Getting Started)
Basic familiarity with Nix syntax
What You'll Build¶

A simple "hello world" systemd service that runs when System Manager activates your configuration.

Step 1: Create a Service Module¶

Create a file called hello-service.nix in your ~/.config/system-manager/ folder:


{ lib, pkgs, ... }:
{
  config = {
    nixpkgs.hostPlatform = "x86_64-linux";

    systemd.services.say-hello = {
      description = "say-hello";
      enable = true;
      wantedBy = [ "system-manager.target" ];
      serviceConfig = {
        Type = "oneshot";
        RemainAfterExit = true;
      };
      script = ''
        ${lib.getBin pkgs.hello}/bin/hello
      '';
    };
  };
}
Let's break down what each part does:

systemd.services.say-hello: Creates a service named "say-hello".
enable = true: Activates the service.
wantedBy = [ "system-manager.target" ]: Starts the service when System Manager activates.
Type = "oneshot": Runs once and exits.
RemainAfterExit = true: Keeps the service marked as "active" after the script finishes.
script: The actual command to run.
Step 2: Add the Module to Your Flake¶

Edit your flake.nix and add the new module:


modules = [
    ./system.nix
    ./hello-service.nix
];
Step 3: Apply the Configuration¶

Run System Manager to activate your new service:


nix run 'github:numtide/system-manager' -- switch --flake . --sudo
Step 4: Verify It Worked¶

Check the journal to see if your service ran:


journalctl -n 20
You should see output like:


Nov 18 12:12:51 my-ubuntu systemd[1]: Starting say-hello.service - say-hello...
Nov 18 12:12:51 my-ubuntu say-hello-start[3488278]: Hello, world!
Nov 18 12:12:51 my-ubuntu systemd[1]: Finished say-hello.service - say-hello.
Step 5: Check the Generated Service File¶

System Manager created a systemd unit file at /etc/systemd/system/say-hello.service. You can view it with:


cat /etc/systemd/system/say-hello.service
Next Steps¶

Learn about service configuration options in the reference.
See the timer example for scheduled services.
Explore the nginx example for a real-world service.
January 18, 2026
 
January 10, 2026
Previous
Getting Started
Next
Multi-file Configuration
Content on this site is licensed under a Creative Commons Attribution-ShareAlike 4.0 International license.

