So recently, I signed up for Oracle Cloud again. I had an account previously, but that got terminated due to some other reasons. I wanted to get a new instance up and running.

Currently, there is some confusion around the Oracle Cloud free tier. Initially, the ARM free tier was 4 OCPUs and 24 GB RAM, but it seems like they are reducing it to 2 OCPUs. That is not confirmed yet, but changes are happening in the backend.

Anyway, I wanted to run NixOS on an Oracle Cloud ARM machine. I tried different tutorials, but most of them were 2 or 3 years old and I couldn’t get them working. So this is how I finally got it done.


The Setup

I already had a Terraform setup for creating the Oracle Cloud Infra. It basically gave me an ARM instance with 4 OCPUs and 24 GB RAM running Ubuntu. When you provision with Terraform, it gives you a standard OS image — in my case, Ubuntu.

The instance type was initially A2.Flex, but I changed it to A1.Flex because A1.Flex is the ARM shape with the free tier. A2.Flex is the newer version without a free tier.

So the starting point was an Oracle Cloud ARM instance running Ubuntu, and the goal was to get NixOS on it.


But Why Not Just Use NixOS Directly?

If you work with cloud, you might ask — why not directly select NixOS while creating the instance?

The reason is simple. There is no NixOS image available on Oracle Cloud. When creating an instance, you can select images like Oracle Linux, Ubuntu, Red Hat, CentOS, Windows, AlmaLinux, or Rocky Linux. But no NixOS.

So we need to figure out another way to install it.

This is not something new that I discovered. People have been doing this for a while. The beauty of NixOS is that everything is defined in configuration. You don’t forget anything you have installed. Everything stays neat and tidy. If you manage a lot of infrastructure, you might love NixOS because you can just take your configuration wherever you need it.

Since Oracle was not providing a NixOS image, we need to install it ourselves using kexec.


The Plan

Here is what we are going to do:

  1. Start with the Ubuntu server.
  2. Install the Nix CLI on Ubuntu.
  3. Build a temporary NixOS kexec environment.
  4. Boot into that temporary environment using kexec.
  5. Wipe the Ubuntu disk.
  6. Partition and format the disk for NixOS.
  7. Install NixOS permanently.
  8. Reboot into the final NixOS system.

Before you start — please take a backup. This process will wipe everything on the boot disk. If you are on Oracle Cloud, create a boot volume backup first.


SSH into the Server

Once your instance is created, make sure you can SSH into it. Since I am using Terraform, I have already provided my SSH key in the Terraform code itself. So I can SSH in without any additional passwords.

ssh ubuntu@<YOUR_PUBLIC_IP>

Once I was in, I confirmed the architecture:

uname -m

It returned aarch64, which confirms this is an ARM machine. This is important because we need to build everything for ARM, not x86.


Install the Nix CLI on Ubuntu

Now we have a server running Ubuntu. We need to install some basic tools for Nix, kexec, and disk partitioning:

sudo apt update
sudo apt install -y curl git xz-utils kexec-tools parted gdisk dosfstools e2fsprogs

Next, install the Nix package manager as a daemon:

sh <(curl -L https://nixos.org/nix/install) --daemon

After installation, close and reopen your SSH session so that Nix is loaded into your shell. Then add the NixOS channel:

nix-channel --add https://nixos.org/channels/nixos-26.05 nixpkgs
nix-channel --update

I used version 26.05 here, but you can use whatever the latest version is at the time.

Verify Nix is working:

nix --version

Build the kexec Configuration

Now we have the Nix CLI installed on top of Ubuntu. What we need to do next is build a NixOS kexec configuration.

In your home directory, create a kexec.nix file:

nano kexec.nix

Paste this configuration:

{ pkgs, ... }:

{
  imports = [
    <nixpkgs/nixos/modules/installer/netboot/netboot-minimal.nix>
  ];

  users.users.root.openssh.authorizedKeys.keys = [
    "YOUR_PUBLIC_SSH_KEY"
  ];

  services.openssh.enable = true;
  services.openssh.settings.PermitRootLogin = "yes";

  system.stateVersion = "26.05";
}

You need to edit two things here:

  1. Replace YOUR_PUBLIC_SSH_KEY with your actual SSH public key.
  2. Set the system.stateVersion to whatever version you are using.

Now build it:

nix-build '<nixpkgs/nixos>' \
  --arg configuration ./kexec.nix \
  --attr config.system.build.kexecTree

If the build succeeds, it will create a result symlink in your current directory:

ls -la result

Boot into the Temporary NixOS Environment

Now we are going to boot into this temporary NixOS environment. Copy the build output to a temporary directory and run the kexec boot:

mkdir -p /tmp/kexec-nixos
cp -L result/* /tmp/kexec-nixos/
cd /tmp/kexec-nixos
sudo ./kexec-boot

It won’t show much output. Wait for a few seconds and your SSH session will automatically disconnect. That is expected — the server is booting into the temporary NixOS environment.


Reconnect to the Temporary NixOS

Since we booted into a completely different environment, the SSH host key of the machine has changed. If you try to SSH in, it will fail because the host key doesn’t match anymore.

First, remove the old host key from your machine:

ssh-keygen -R <YOUR_PUBLIC_IP>

Now connect as root:

ssh root@<YOUR_PUBLIC_IP>

You should be inside the temporary NixOS environment now. You can confirm this by checking:

cat /etc/os-release

It should show NixOS.


Wipe the Disk and Create Partitions

We are now in a temporary NixOS running entirely in memory. The actual disk still has the Ubuntu installation on it. We need to wipe it and prepare it for NixOS.

I used 50 GB for my root volume. Check the disk layout:

lsblk

In my case, it looked like this:

NAME    MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
loop0     7:0    0 499.1M  0 loop /nix/.ro-store
sda       8:0    0    50G  0 disk
├─sda1    8:1    0    49G  0 part
├─sda15   8:15   0    99M  0 part
└─sda16 259:0    0   923M  0 part

/dev/sda is the boot disk with the Ubuntu installation. We need to wipe this entire disk.

DISK=/dev/sda
wipefs -a "$DISK"
sgdisk --zap-all "$DISK"
partprobe "$DISK"

Now create the new partitions:

parted -s "$DISK" mklabel gpt
parted -s "$DISK" mkpart ESP fat32 1MiB 512MiB
parted -s "$DISK" set 1 esp on
parted -s "$DISK" mkpart root ext4 512MiB 100%
partprobe "$DISK"

Format and mount them:

mkfs.fat -F32 /dev/sda1
mkfs.ext4 -F /dev/sda2
mount /dev/sda2 /mnt
mkdir -p /mnt/boot
mount /dev/sda1 /mnt/boot

You can verify with lsblk that /dev/sda2 is mounted on /mnt and /dev/sda1 on /mnt/boot.


Install NixOS to the Disk

Now we have the disk ready. We need to generate the NixOS configuration:

nixos-generate-config --root /mnt

This creates the configuration files inside /mnt/etc/nixos/. Now edit the main configuration:

nano /mnt/etc/nixos/configuration.nix

Since this is just a server, I don’t need a desktop environment or anything fancy. I just want SSH access, a normal admin user, and a clean setup. I used ubuntu as the username so it feels similar to the original cloud image.

Here is the configuration I used — it is pretty lightweight and you can strip it down even further if you want:

{ config, lib, pkgs, ... }:

{
  imports = [
    ./hardware-configuration.nix
  ];

  boot.loader.systemd-boot.enable = false;

  boot.loader.grub = {
    enable = true;
    efiSupport = true;
    device = "nodev";
    efiInstallAsRemovable = true;
  };

  boot.loader.efi.canTouchEfiVariables = false;

  boot.kernelParams = [
    "console=ttyS0,115200n8"
    "console=tty1"
  ];

  networking.hostName = "oci-nixos";
  networking.networkmanager.enable = true;

  time.timeZone = "Asia/Kolkata";

  services.openssh.enable = true;
  services.openssh.openFirewall = true;

  services.openssh.settings = {
    PasswordAuthentication = false;
    KbdInteractiveAuthentication = false;
    PermitRootLogin = "no";
  };

  users.mutableUsers = false;

  users.users.root = {
    hashedPassword = "!";
    openssh.authorizedKeys.keys = [ ];
  };

  users.users.ubuntu = {
    isNormalUser = true;

    extraGroups = [
      "wheel"
      "networkmanager"
    ];

    hashedPassword = "!";

    openssh.authorizedKeys.keys = [
      "YOUR_PUBLIC_SSH_KEY"
    ];
  };

  security.sudo.wheelNeedsPassword = false;

  environment.systemPackages = with pkgs; [
    nano
    vim
    curl
    wget
    git
    htop
    tmux
    parted
    gptfdisk
  ];

  system.stateVersion = "26.05";
}

You need to change:

  • YOUR_PUBLIC_SSH_KEY — your actual SSH public key.
  • system.stateVersion — the NixOS version you are using.
  • ubuntu — the username, if you want something different.

Now install NixOS to the disk:

nixos-install --no-root-password

This will take some time. Once it completes without errors, NixOS is installed.


Reboot and Verify

Reboot the server:

reboot

Your SSH session will disconnect again. At this point, there are two possibilities — either the server boots successfully into NixOS, or it fails to boot. If it fails, you may need the Oracle Cloud serial console or your boot volume backup.

If it succeeds, the host key will change one more time. Remove it again:

ssh-keygen -R <YOUR_PUBLIC_IP>

Now SSH in with the username you configured:

ssh ubuntu@<YOUR_PUBLIC_IP>

Once you are in, you can verify that it is running NixOS using something like fastfetch:

nix-shell -p fastfetch --run fastfetch

That’s It

This gave me an Oracle Cloud ARM instance running NixOS with SSH enabled, root login disabled, and a clean minimal configuration. This approach worked for me after several older tutorials failed.

If you are trying to do the same thing, I hope this gives you a clear starting point. You can tweak the configuration.nix later based on what you need.

Let me know if you have any questions or run into issues.