GuidesDeploy NixOS to AWS using Determinate

Deploy NixOS to AWS using Determinate

This guide shows you how to run NixOS systems on Amazon Web Services (AWS) using Determinate Systems’ provide Amazon Machine Images (AMIs).

This guide involves publishing NixOS configurations to FlakeHub Cache, which requires you to sign up for a paid plan for FlakeHub.

NixOS AMIs

Determinate offers custom AMIs for NixOS on both AMD64 Linux (x86_64-linux) and ARM64 Linux (aarch64-linux). You can see the code for these AMIs in the DeterminateSystems/nixos-amis repository on GitHub. On both systems, the AMIs have these tools installed:

  • Determinate Nix, which includes Determinate Nixd, which enables you to log in to FlakeHub from AWS using only this command:

    Log in to FlakeHub from AWS using Determinate Nixd
    determinate-nixd login aws

    Authentication requires no static keys because Determinate uses AWS’s Security Token Service (STS) to authenticate dynamically. For more on STS, see our dedicated guide.

  • fh, the CLI for FlakeHub. fh provides functionality like the fh apply nixos command, which enables you to apply NixOS configurations pushed to FlakeHub Cache without needing to even evaluate the configuration’s Nix expression (using a feature called resolved store paths). Here’s an example command:

    Apply a NixOS configuration from a resolved store path
    sudo fh apply nixos "my-org/my-flake/*#nixosConfigurations.my-nixos-configuration-output"

Having Determinate Nix and fh on the AMI enables you to apply a NixOS configuration in just two commands. Here’s an example:

Apply a NixOS configuration to the AMI
determinate-nixd login aws
sudo fh apply nixos "my-org/my-flake/*#nixosConfigurations.my-nixos-configuration-output"

In order to use fh apply nixos in this way, however, you need to publish your NixOS configuration to FlakeHub.

Publish NixOS configurations to FlakeHub

In order to use the fh apply nixos command, you’ll need to publish a flake to FlakeHub that has a NixOS configuration output. Here’s a brief example:

flake.nix
{
  inputs.nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0"; # stable Nixpkgs
 
  outputs =
    { self, ... }@inputs:
    {
      nixosConfigurations.my-nixos-system = inputs.nixpkgs.lib.nixosSystem {
        # system configuration
      };
    };
}

You can publish that flake to FlakeHub on GitHub using the flakehub-push Action.

Determinate also supports Buildkite and Semaphore for CI.

Here’s an example configuration:

.github/workflows/flakehub-push.yaml
name: Publish every Git push to main to FlakeHub
 
on:
  push:
    branches:
      - main
 
jobs:
  flakehub-publish:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v5
      - uses: DeterminateSystems/determinate-nix-action@v3
      - uses: DeterminateSystems/flakehub-push@main
        with:
          rolling: true
          visibility: private # can also be public
          include-output-paths: true

The permissions block is necessary and you must set include-output-paths: true to take advantage of resolved store paths.

As an alternative to writing your own YAML, you can use our publishing wizard to generate your flakehub-push configuration.

In addition to publishing your flake to FlakeHub, you’ll need to push your NixOS configuration to FlakeHub Cache.

Push your NixOS configuration to FlakeHub Cache

In addition to publishing your NixOS configuration to FlakeHub, you’ll need to push the configuration’s closure to FlakeHub Cache. When you use the flakehub-cache-action, as in the example below, the closure is automatically pushed to the cache when you build the NixOS system’s toplevel. Here’s an example for the flake above:

.github/workflows/flakehub-push.yaml
name: Push NixOS system to FlakeHub Cache
 
on:
  push:
    branches:
      - main
 
jobs:
  flakehub-publish:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v5
      - uses: DeterminateSystems/determinate-nix-action@v3
      - uses: DeterminateSystems/flakehub-cache-action@v3
      - name: Build NixOS toplevel
        run: nix build .#nixosConfigurations.my-nixos-system.config.system.build.toplevel

An alternative to explicitly building/caching your NixOS closures, you can use Determinate CI, which automatically builds/caches all of your flake’s derivation outputs.

Once you’ve pushed your NixOS closure to the cache, you can set up deployment.

Run a deploy script

There’s a wide variety of ways to deploy AMIs to AWS, and we won’t cover that here, although we have a demo repository that shows you how to do it with Terraform. Generally speaking, you’ll need to add a deploy script as user data, like this one from above:

NixOS deployment script
determinate-nixd login aws
sudo fh apply nixos "my-org/my-flake/0.1#nixosConfigurations.my-nixos-configuration-output"

This script, for example, would deploy EtherCalc, as in the demo:

NixOS deployment script
determinate-nixd login aws
sudo fh apply nixos "DeterminateSystems/demo/0.1#nixosConfigurations.ethercalc-demo"

If you’re a Terraform user, you could set things up along these lines:

aws.tf
# Declare the flake reference for your NixOS configuration
locals {
  flake_reference = "my-org/my-flake/0.1#nixosConfigurations.my-nixos-configuration-output"
}
 
# Select our NixOS AMI
data "aws_ami" "determinate_nixos_ami" {
  most_recent = true
  owners      = ["535002876703"]
 
  filter {
    name   = "name"
    values = ["determinate/nixos/epoch-1/24.05.*"]
  }
 
  filter {
    name   = "architecture"
    values = ["x86_64"] # aarch64-linux also available
  }
}
 
# Declare an AWS instance using the AMI
resource "aws_instance" "demo" {
  ami                         = data.aws_ami.determinate_nixos_ami.id
  user_data                   = <<-USERDATA
#!/bin/sh
 
determinate-nixd login aws
 
fh apply nixos "${local.flake_reference}"
USERDATA
 
  # other options
}

Once you’ve deployed your AMI, you can now trigger updates by (a) publishing a new flake release and pushing the new closure to FlakeHub Cache and then (b) re-running the user data script. Updating the system should be quite speedy, as the closure is cached and Determinate Nix doesn’t even need to calculate the closure’s store path, as FlakeHub has already done that. For automating re-running that script, we recommend using AWS Systems Manager.