<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>lelgenio</title>
    <link>https://blog.lelgenio.com/</link>
    <description></description>
    <pubDate>Sat, 18 Apr 2026 22:09:58 -0300</pubDate>
    <item>
      <title>Deploying NixOS with flakes on Digital Ocean</title>
      <link>https://blog.lelgenio.com/deploying-nixos-with-flakes-on-digital-ocean</link>
      <description>&lt;![CDATA[So, yesterday I spent my afternoon playing around with running a NixOS system on Digital Ocean. Since everyone agrees that lack of documentation is the #1 problem for NixOS, I feel it&#39;s appropriate to share my little adventure.&#xA;&#xA;What are we doing exactly?&#xA;&#xA;We will be setting up a single instance of NixOS, so no orchestration, this is not enough for any production service. It&#39;s enough to run this blog though :).&#xA;We&#39;ll be using Digital Ocean, because it&#39;s what I&#39;m used to working with, but there are many other options, Instructions should be similar enough if you wish to use something different.&#xA;We&#39;ll be using flakes to easily pin the version of all packages, and because flake outputs have a common and organized schema for their outputs.&#xA;The idea here is that you should be able to edit you server&#39;s configuration locally and deploy it to your server over ssh; You&#39;ll never upload any nix file to your server.&#xA;I&#39;ll be introducing some less self-evident features of nix/nixos.&#xA;&#xA;Basics of Flakes&#xA;&#xA;Flakes are a feature of Nix which provides a common way of organizing you software repository as a function that takes other repositories as input, and produces some output; That output may be packages, NixOS modules, arbitrary Nix code, anything.&#xA;&#xA;Flakes only provide a different entry-point to nix code, when migrating a codebase to flakes you will only likely add flake.nix and make very few changes to other files. &#xA;&#xA;Enabling flakes&#xA;&#xA;For historical reasons Flakes are marked as &#34;experimental&#34;.&#xA;&#xA;If you are not using NixOS, you can enable flakes by editing /etc/nix/nix.conf and adding this line:&#xA;experimental-features = nix-command flakes&#xA;&#xA;If you a are using NixOS, add his line to your configuration:&#xA;&#xA;nix.extraOptions = &#39;&#39;&#xA;  experimental-features = nix-command flakes&#xA;&#39;&#39;;&#xA;&#xA; If you only wish to try Flakes temporarily, you can override the nix settings on the command line:&#xA;&#xA;alias nix=&#34;nix --extra-experimental-features &#39;nix-command flakes&#39;&#34;&#xA;nix run github:NixOS/nixpkgs/release-23.05#hello&#xA;This command should work even if you don&#39;t have flakes enabled.&#xA;&#xA;nixos-rebuild should be able to use flakes with no configuration.&#xA;&#xA;A basic Flake&#xA;&#xA;Here is a basic flake, I added some comments to quickly go over some important details.&#xA;&#xA;./flake.nix&#xA;A Flake file MUST be an Attribute set&#xA;{&#xA;  # inputs MUST be an Attribute set that lists the .... You know&#xA;  inputs = {&#xA;    # Here we declare we need nixpkgs, notice we only say what branch&#xA;    # not the exact commit hash, flakes will take care of that &#xA;    nixpkgs.url = &#34;github:NixOS/nixpkgs/nixos-23.05&#34;;&#xA;  };&#xA;  # outputs MUST be a function that returns an Attribute set.&#xA;  # Everything else is just convention, convention that even some&#xA;  # Nix commands will assume are true.&#xA;  outputs = inputs: {&#xA;    # nix run and nix build will look for this attribute by default&#xA;    # nix run or nix run .#default should print &#34;Hello world&#34;&#xA;    packages.x8664-linux.default = &#xA;      inputs.nixpkgs.packages.x8664-linux.hello;&#xA;&#xA;    # You can switch to the system described in ./configuration with&#xA;    # nixos-rebuild switch --flake .#hal9000&#xA;    nixosConfigurations.hal9000 = inputs.nixpkgs.lib {&#xA;      system = &#34;x8664-linux&#34;;&#xA;      modules = [ ./configuration.nix ];&#xA;    };&#xA;  };&#xA;}&#xA;&#xA;For Flakes to &#34;see&#34; a file it must be committed/added in git! If the file is not added, you will get cryptic &#34;file not found&#34; errors. &#xA; &#xA;Whenever you use flakes, a file called flake.lock will be generated, you should commit it, it keeps track of the exact hash of the inputs and keeps your builds reproducible.&#xA;&#xA;Now you can build any piece of your output attribute set:&#xA;$ nix build &#34;.#packages.x8664-linux.default&#34;&#xA;$ ls -l result/bin/&#xA;.r-xr-xr-x 56k root  1 jan  1970 hello&#xA;&#xA;$ nix run&#xA;Hello, world!&#xA;&#xA;nixos-rebuild internally calls this command&#xA;$ nix build &#34;.#nixosConfigurations.hal9000.config.system.build.toplevel&#34;&#xA;$ ls result/&#xA;activate               firmware                nixos-version&#xA;append-initrd-secrets  init                    prepare-root&#xA;bin                    init-interface-version  specialisation&#xA;boot.json              initrd                  sw&#xA;dry-activate           kernel                  system&#xA;etc                    kernel-modules          systemd&#xA;extra-dependencies     kernel-params&#xA;&#xA;Introducing Digital Ocean Droplets&#xA;&#xA;Droplets are Digital Ocean&#39;s Virtual Private Server (VPS), they can be rented for as low as 4 USD/month. There&#39;s really not much to say about it, it&#39;s a server, you get a full dedicated IP address, all ports are open, you have root access and can do as you wish.&#xA;&#xA;Unfortunately they do not have a one-click solution for NixOS, fortunately they do allow you to upload a custom OS image, we&#39;ll go with that.&#xA;&#xA;Writing a basic NixOS configuration for a VPS&#xA;&#xA;This initial configuration is only supposed to get our server running and allow us to connect to it via SSH.&#xA;If you don&#39;t have ssh keys, now&#39;s the time to generate them!&#xA;&#xA;&lt;div class=&#34;warning&#34; style=&#34;&#xA;padding: 10px;&#xA;border-radius: 10px;&#xA;white-space: normal;&#xA;color: #856404;&#xA;background-color: #fff3cd;&#xA;border: 2px solid #ffeeba;&#xA;&#34;  bWarning/bbr&#xA;Be very careful of what you put in your nix configuration!br&#xA;Assume anything here will be written as plain text on your server!&#xA;/div&#xA;&#xA;Here&#39;s the configuration I recommend you use, fill in your ssh public key, and optionally your hashed password:&#xA;./configuration.nix&#xA;{ config, pkgs, lib, ... }: {&#xA;  services.openssh = {&#xA;    enable = true;&#xA;    settings.PasswordAuthentication = false;&#xA;  };&#xA;  users.users.root = {&#xA;    openssh.authorizedKeys.keys = [&#xA;      &#34;the contents of ~/.ssh/idrsa.pub go here&#34;&#xA;    ];&#xA;    # Altough optional, setting a root password allows you to&#xA;    # open a terminal interface in DO&#39;s website.&#xA;    hashedPassword = &#xA;      &#34;generate a hashed password with the mkpasswd command&#34;;&#xA;  };&#xA;&#xA;  # You should always have some swap space,&#xA;  # This is even more important on VPSs&#xA;  # The swapfile will be created automatically.&#xA;  swapDevices = [{&#xA;    device = &#34;/swap/swapfile&#34;;&#xA;    size = 1024 * 2; # 2 GB&#xA;  }];&#xA;&#xA;  system.stateVersion = &#34;23.05&#34;; # Never change this&#xA;}&#xA;&#xA;Customizing NixOS module inputs&#xA;&#xA;Before we keep going, I want to share something important for keeping your modules organized.&#xA;You might be familiar with how you can pass values to be used in nixos modules:&#xA;&#xA;{ config, pkgs, lib, ... }: {&#xA;↑ Like this&#xA;}&#xA;These values are set by default by nixpkgs.lib, but what if you need something else... Like your Flakes&#39; inputs? Good news, using specialArgs, you can!&#xA;&#xA;./flake.nix&#xA;{&#xA;  # ....&#xA;  outputs = inputs: {&#xA;    nixosConfigurations.hal9000 = inputs.nixpkgs.lib {&#xA;      specialArgs = {&#xA;        # remember &#34;inherit x;&#34; is the same as &#34;x = x;&#34;&#xA;        inherit inputs; &#xA;      };&#xA;      system = &#34;x8664-linux&#34;;&#xA;      modules = [ ./configuration.nix ];&#xA;    };&#xA;  };&#xA;}&#xA;&#xA;Now to use it, as an example we&#39;ll tell nixos to install the nixpkgs repository under /etc/nixpkgs:&#xA;&#xA;{ config, pkgs, lib, inputs, ... }: {&#xA;  environment.etc.nixpkgs.source = inputs.nixpkgs;&#xA;}&#xA;&#xA;Creating a NixOS image for Digital Ocean&#xA;&#xA;To get started, we&#39;ll need to create a DO Image. To do that, import the digital-ocean-image, you should also compress your image as much as possible, Digital Ocean takes a bit to process your image, so this should reduce your waiting.&#xA;&#xA;You should keep the module imported even after you have generated the image, so you can update your server later.&#xA;&#xA;./configuration.nix&#xA;{ config, pkgs, inputs, ... }: {&#xA;  imports = [&#xA;    &#34;${inputs.nixpkgs}/nixos/modules/virtualisation/digital-ocean-image.nix&#34;&#xA;  ];&#xA;&#xA;  # Use more aggressive compression then the default.&#xA;  virtualisation.digitalOceanImage.compressionMethod = &#34;bzip2&#34;;&#xA;  &#xA;  # ...&#xA;}&#xA;&#xA;Now for let&#39;s build that image, this may take a few minutes since it temporarily spawns a virtual machine:&#xA;&#xA;$ nix build .#nixosConfigurations.hal9000.config.system.build.digitalOceanImage&#xA;$ ls result/&#xA;nixos.qcow2.bz2&#xA;&#xA;Uploading that image and starting our server&#xA;&#xA;At least on Digital Ocean, to start a droplet with a custom image, you must first upload that image on the same region as you wish to run the droplet.&#xA;&#xA;You can upload your image at https://cloud.digitalocean.com/images/custom_images.&#xA;That process can take quite some time to finish (10+ minutes).&#xA;&#xA;After that&#39;s done, Just click on &#34;more&#34;   &#34;Start Droplet&#34;.  Fill in the form as you see fit.&#xA;For reasons beyond my understanding, you must use SSH key authentication on this form.&#xA;&#xA;After that&#39;s done, you&#39;ll get an IP address, since you added your public key to the configuration, try connecting to it:&#xA;$ ssh root@123.123.123.123 &#39;echo Hi from $(hostname)!&#39;&#xA;Hi from hal9000&#xA;&#xA;How to update that server, after it&#39;s running&#xA;&#xA;Now that we have a running server, how will we update it?&#xA;We need a tool that can: &#xA;&#xA;Connect to our server via ssh&#xA;Intelligently copy nix paths&#xA;run the activation script &#xA;&#xA;What if I told you, that tool is already installed on your system?&#xA;&#xA;That tool is nixos-rebuild! You just need to use the --target-host flag:&#xA;&#xA;nixos-rebuild switch --flake .#hal9000 --target-host root@123.123.123.123&#xA;&#xA;Closing thoughts&#xA;&#xA;If you got here, I recommend you get a domain name and point it at your server, this allows you to easily setup ACME certificate generation.&#xA;&#xA;Phew! Done right? Nope! Now you need to actually use your server for something. The vast collection of modules in NixOS should make this the most pleasing part of the setup. It&#39;s up to you now!&#xA;&#xA;Have fun with your new server!&#xA;&#xA;services.nginx = {&#xA;  enable = true;&#xA;  virtualHosts.&#34;hal9000.example&#34; = {&#xA;    enableACME = true;&#xA;    forceSSL = true;&#xA;    root = pkgs.runCommand &#34;www-dir&#34; { } &#39;&#39;&#xA;      mkdir -p $out&#xA;      cat   $out/index.html &lt;&lt;EOF&#xA;        !DOCTYPE html&#xA;        html lang=&#34;en&#34;&#xA;        body&#xA;          h1&#xA;              I&#39;m sorry Dave, I&#39;m afraid this&#xA;              pop culture reference is overused.&#xA;          h1&#xA;        /body&#xA;        /html&#xA;      EOF&#xA;    &#39;&#39;;&#xA;  };&#xA;};&#xA;`]]&gt;</description>
      <content:encoded><![CDATA[<p>So, yesterday I spent my afternoon playing around with running a NixOS system on Digital Ocean. Since everyone agrees that lack of documentation is the #1 problem for NixOS, I feel it&#39;s appropriate to share my little adventure.</p>

<h2 id="what-are-we-doing-exactly">What are we doing exactly?</h2>
<ul><li>We will be setting up a <strong>single</strong> instance of NixOS, so no orchestration, this is <strong>not</strong> enough for any production service. It&#39;s enough to run this blog though :).</li>
<li>We&#39;ll be using Digital Ocean, because it&#39;s what I&#39;m used to working with, but there are <a href="https://nixos.wiki/wiki/NixOS_friendly_hosters">many other options</a>, Instructions should be similar enough if you wish to use something different.</li>
<li>We&#39;ll be using flakes to easily pin the version of all packages, and because flake outputs have a common and organized schema for their outputs.</li>
<li>The idea here is that you should be able to edit you server&#39;s configuration locally and deploy it to your server over ssh; You&#39;ll never upload any nix file to your server.</li>
<li>I&#39;ll be introducing some less self-evident features of nix/nixos.</li></ul>

<h2 id="basics-of-flakes">Basics of Flakes</h2>

<p>Flakes are a feature of Nix which provides a common way of organizing you software repository as a function that <strong>takes other repositories as input</strong>, and produces some output; That output may be packages, NixOS modules, arbitrary Nix code, anything.</p>

<p>Flakes only provide a different entry-point to nix code, when migrating a codebase to flakes you will only likely add <code>flake.nix</code> and make very few changes to other files.</p>

<h3 id="enabling-flakes">Enabling flakes</h3>

<p>For historical reasons Flakes are marked as “experimental”.</p>

<p>If you are <strong>not</strong> using NixOS, you can enable flakes by editing <code>/etc/nix/nix.conf</code> and adding this line:</p>

<pre><code class="language-toml">experimental-features = nix-command flakes
</code></pre>

<p>If you a <strong>are</strong> using NixOS, add his line to your configuration:</p>

<pre><code class="language-nix">nix.extraOptions = &#39;&#39;
  experimental-features = nix-command flakes
&#39;&#39;;
</code></pre>

<p> If you only wish to try Flakes temporarily, you can override the nix settings on the command line:</p>

<pre><code class="language-sh">alias nix=&#34;nix --extra-experimental-features &#39;nix-command flakes&#39;&#34;
nix run github:NixOS/nixpkgs/release-23.05#hello
</code></pre>

<p>This command should work even if you don&#39;t have flakes enabled.</p>

<p><code>nixos-rebuild</code> should be able to use flakes with no configuration.</p>

<h3 id="a-basic-flake">A basic Flake</h3>

<p>Here is a basic flake, I added some comments to quickly go over some important details.</p>

<pre><code class="language-nix"># ./flake.nix
# A Flake file MUST be an Attribute set
{
  # inputs MUST be an Attribute set that lists the .... You know
  inputs = {
    # Here we declare we need nixpkgs, notice we only say what branch
    # not the exact commit hash, flakes will take care of that 
    nixpkgs.url = &#34;github:NixOS/nixpkgs/nixos-23.05&#34;;
  };
  # outputs MUST be a function that returns an Attribute set.
  # Everything else is just convention, convention that even some
  # Nix commands will assume are true.
  outputs = inputs: {
    # `nix run` and `nix build` will look for this attribute by default
    # `nix run` or `nix run .#default` should print &#34;Hello world&#34;
    packages.x86_64-linux.default = 
      inputs.nixpkgs.packages.x86_64-linux.hello;

    # You can switch to the system described in ./configuration with
    # `nixos-rebuild switch --flake .#hal9000`
    nixosConfigurations.hal9000 = inputs.nixpkgs.lib {
      system = &#34;x86_64-linux&#34;;
      modules = [ ./configuration.nix ];
    };
  };
}
</code></pre>

<p>For Flakes to “see” a file it <em>must</em> be committed/added in git! If the file is not added, you will get cryptic “file not found” errors.</p>

<p>Whenever you use flakes, a file called <code>flake.lock</code> will be generated, you should commit it, it keeps track of the exact hash of the inputs and keeps your builds reproducible.</p>

<p>Now you can build any piece of your output attribute set:</p>

<pre><code class="language-sh">$ nix build &#34;.#packages.x86_64-linux.default&#34;
$ ls -l result/bin/
.r-xr-xr-x 56k root  1 jan  1970 hello

$ nix run
Hello, world!

# nixos-rebuild internally calls this command
$ nix build &#34;.#nixosConfigurations.hal9000.config.system.build.toplevel&#34;
$ ls result/
activate               firmware                nixos-version
append-initrd-secrets  init                    prepare-root
bin                    init-interface-version  specialisation
boot.json              initrd                  sw
dry-activate           kernel                  system
etc                    kernel-modules          systemd
extra-dependencies     kernel-params
</code></pre>

<h2 id="introducing-digital-ocean-droplets">Introducing Digital Ocean Droplets</h2>

<p>Droplets are Digital Ocean&#39;s Virtual Private Server (VPS), they can be rented for as low as 4 USD/month. There&#39;s really not much to say about it, it&#39;s a server, you get a full dedicated IP address, all ports are open, you have root access and can do as you wish.</p>

<p>Unfortunately they do not have a one-click solution for NixOS, fortunately they do allow you to upload a custom OS image, we&#39;ll go with that.</p>

<h2 id="writing-a-basic-nixos-configuration-for-a-vps">Writing a basic NixOS configuration for a VPS</h2>

<p>This initial configuration is only supposed to get our server running and allow us to connect to it via SSH.
If you don&#39;t have ssh keys, now&#39;s the time to generate them!</p>

<div class="warning" style="
padding: 10px;
border-radius: 10px;
white-space: normal;
color: #856404;
background-color: #fff3cd;
border: 2px solid #ffeeba;
">
<b>Warning</b><br>
Be very careful of what you put in your nix configuration!<br>
Assume anything here will be written as plain text on your server!
</div>

<p>Here&#39;s the configuration I recommend you use, fill in your ssh <strong>public</strong> key, and optionally your <strong>hashed</strong> password:</p>

<pre><code class="language-nix"># ./configuration.nix
{ config, pkgs, lib, ... }: {
  services.openssh = {
    enable = true;
    settings.PasswordAuthentication = false;
  };
  users.users.root = {
    openssh.authorizedKeys.keys = [
      &#34;the contents of ~/.ssh/id_rsa.pub go here&#34;
    ];
    # Altough optional, setting a root password allows you to
    # open a terminal interface in DO&#39;s website.
    hashedPassword = 
      &#34;generate a hashed password with the mkpasswd command&#34;;
  };

  # You should always have some swap space,
  # This is even more important on VPSs
  # The swapfile will be created automatically.
  swapDevices = [{
    device = &#34;/swap/swapfile&#34;;
    size = 1024 * 2; # 2 GB
  }];

  system.stateVersion = &#34;23.05&#34;; # Never change this
}
</code></pre>

<h2 id="customizing-nixos-module-inputs">Customizing NixOS module inputs</h2>

<p>Before we keep going, I want to share something important for keeping your modules organized.
You might be familiar with how you can pass values to be used in nixos modules:</p>

<pre><code class="language-nix">{ config, pkgs, lib, ... }: {
# ↑ Like this
}
</code></pre>

<p>These values are set by default by nixpkgs.lib, but what if you need something else... Like your Flakes&#39; inputs? Good news, using <code>specialArgs</code>, you can!</p>

<pre><code class="language-nix"># ./flake.nix
{
  # ....
  outputs = inputs: {
    nixosConfigurations.hal9000 = inputs.nixpkgs.lib {
      specialArgs = {
        # remember &#34;inherit x;&#34; is the same as &#34;x = x;&#34;
        inherit inputs; 
      };
      system = &#34;x86_64-linux&#34;;
      modules = [ ./configuration.nix ];
    };
  };
}
</code></pre>

<p>Now to use it, as an example we&#39;ll tell nixos to install the nixpkgs repository under <code>/etc/nixpkgs</code>:</p>

<pre><code class="language-nix">{ config, pkgs, lib, inputs, ... }: {
  environment.etc.nixpkgs.source = inputs.nixpkgs;
}
</code></pre>

<h2 id="creating-a-nixos-image-for-digital-ocean">Creating a NixOS image for Digital Ocean</h2>

<p>To get started, we&#39;ll need to create a DO Image. To do that, import the <code>digital-ocean-image</code>, you should also compress your image as much as possible, Digital Ocean takes a bit to process your image, so this should reduce your waiting.</p>

<p>You should keep the module imported even after you have generated the image, so you can update your server later.</p>

<pre><code class="language-nix"># ./configuration.nix
{ config, pkgs, inputs, ... }: {
  imports = [
    &#34;${inputs.nixpkgs}/nixos/modules/virtualisation/digital-ocean-image.nix&#34;
  ];

  # Use more aggressive compression then the default.
  virtualisation.digitalOceanImage.compressionMethod = &#34;bzip2&#34;;
  
  # ...
}
</code></pre>

<p>Now for let&#39;s build that image, this may take a few minutes since it temporarily spawns a virtual machine:</p>

<pre><code class="language-sh">$ nix build .#nixosConfigurations.hal9000.config.system.build.digitalOceanImage
$ ls result/
nixos.qcow2.bz2
</code></pre>

<h2 id="uploading-that-image-and-starting-our-server">Uploading that image and starting our server</h2>

<p>At least on Digital Ocean, to start a droplet with a custom image, you must first upload that image on <strong>the same region</strong> as you wish to run the droplet.</p>

<p>You can upload your image at <a href="https://cloud.digitalocean.com/images/custom_images">https://cloud.digitalocean.com/images/custom_images</a>.
That process can take quite some time to finish (10+ minutes).</p>

<p>After that&#39;s done, Just click on “more” &gt; “Start Droplet”.  Fill in the form as you see fit.
For reasons beyond my understanding, you <strong>must</strong> use SSH key authentication on this form.</p>

<p>After that&#39;s done, you&#39;ll get an IP address, since you added your public key to the configuration, try connecting to it:</p>

<pre><code class="language-sh">$ ssh root@123.123.123.123 &#39;echo Hi from $(hostname)!&#39;
Hi from hal9000
</code></pre>

<h2 id="how-to-update-that-server-after-it-s-running">How to update that server, after it&#39;s running</h2>

<p>Now that we have a running server, how will we update it?
We need a tool that can:</p>
<ol><li>Connect to our server via ssh</li>
<li>Intelligently copy nix paths</li>
<li>run the activation script</li></ol>

<p>What if I told you, that tool is already installed on your system?</p>

<p>That tool is <code>nixos-rebuild</code>! You just need to use the <code>--target-host</code> flag:</p>

<pre><code class="language-sh">nixos-rebuild switch --flake .#hal9000 --target-host root@123.123.123.123
</code></pre>

<h2 id="closing-thoughts">Closing thoughts</h2>

<p>If you got here, I recommend you get a domain name and point it at your server, this allows you to easily setup ACME certificate generation.</p>

<p>Phew! Done right? Nope! Now you need to actually use your server for something. The vast collection of modules in NixOS should make this the most pleasing part of the setup. It&#39;s up to you now!</p>

<p>Have fun with your new server!</p>

<pre><code class="language-nix">services.nginx = {
  enable = true;
  virtualHosts.&#34;hal9000.example&#34; = {
    enableACME = true;
    forceSSL = true;
    root = pkgs.runCommand &#34;www-dir&#34; { } &#39;&#39;
      mkdir -p $out
      cat &gt; $out/index.html &lt;&lt;EOF
        &lt;!DOCTYPE html&gt;
        &lt;html lang=&#34;en&#34;&gt;
        &lt;body&gt;
          &lt;h1&gt;
              I&#39;m sorry Dave, I&#39;m afraid this
              pop culture reference is overused.
          &lt;h1&gt;
        &lt;/body&gt;
        &lt;/html&gt;
      EOF
    &#39;&#39;;
  };
};
</code></pre>
]]></content:encoded>
      <guid>https://blog.lelgenio.com/deploying-nixos-with-flakes-on-digital-ocean</guid>
      <pubDate>Mon, 16 Oct 2023 01:06:43 +0000</pubDate>
    </item>
  </channel>
</rss>