This gist documents the essential logic for building Amazon AMIs using Nix flakes and nixos-generators.
The approach uses:
- nixos-generators - Community tool for generating NixOS images in various formats
- Nix flakes - For reproducible, declarative configuration
- Comin - GitOps-style automatic configuration updates
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
nixos-generators = {
url = "github:nix-community/nixos-generators";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils";
# Optional: GitOps for automatic config updates
comin = {
url = "github:nlewo/comin";
inputs.nixpkgs.follows = "nixpkgs";
};
};
}The core function that generates AMIs:
# In flake.nix outputs
makeAmazonAMI =
{ hostname, modules }:
nixos-generators.nixosGenerate {
inherit system;
format = "amazon";
modules = [
# Import the standard NixOS Amazon image module
(
{ modulesPath, ... }:
{
imports = [ "${modulesPath}/virtualisation/amazon-image.nix" ];
virtualisation.diskSize = 16 * 1024; # 16 GB disk
}
)
# Base system configuration
./configuration.nix
# Optional: GitOps support
comin.nixosModules.comin
] ++ modules; # Additional custom modules
};# In flake.nix outputs
packages = {
# Simple AMI
my-server = makeAmazonAMI {
hostname = "my-server";
modules = [ ];
};
# AMI with custom modules
web-server = makeAmazonAMI {
hostname = "web-server";
modules = [
./modules/nginx.nix
./modules/certbot.nix
];
};
# AMI with inline configuration
dev-box = makeAmazonAMI {
hostname = "dev-box";
modules = [
({ pkgs, ... }: {
environment.systemPackages = with pkgs; [ vim git nodejs ];
services.openssh.enable = true;
})
];
};
};{ pkgs, lib, ... }:
{
# Enable flakes
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nixpkgs.config.allowUnfree = lib.mkForce true;
# Base packages
environment.systemPackages = with pkgs; [
git
vim
awscli2
];
# Garbage collection
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 7d";
};
# SSH configuration
services.openssh = {
enable = true;
settings.PasswordAuthentication = false;
settings.KbdInteractiveAuthentication = false;
};
# User account
users.users.admin = {
isNormalUser = true;
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAA... your-key-here"
];
};
# Allow sudo without password for wheel group
security.sudo.wheelNeedsPassword = false;
}# Build a specific AMI target
nix build .#my-server
# The result is a raw disk image at:
# result/nixos-amazon-image-*.rawCreate an IAM role for the VM Import service:
# create-vmimport.sh
ROLE_NAME="vmimport"
BUCKET_NAME="your-ami-bucket"
# Create trust policy
cat > trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "vmie.amazonaws.com" },
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": { "sts:Externalid": "vmimport" }
}
}
]
}
EOF
# Create role
aws iam create-role --role-name $ROLE_NAME --assume-role-policy-document file://trust-policy.json
# Attach policy for S3 and EC2 access
cat > role-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::${BUCKET_NAME}",
"arn:aws:s3:::${BUCKET_NAME}/*"
]
},
{
"Effect": "Allow",
"Action": [
"ec2:ModifySnapshotAttribute",
"ec2:CopySnapshot",
"ec2:RegisterImage",
"ec2:Describe*"
],
"Resource": "*"
}
]
}
EOF
aws iam put-role-policy --role-name $ROLE_NAME --policy-name $ROLE_NAME --policy-document file://role-policy.json# Upload raw image to S3
aws s3 cp result/nixos-amazon-image-*.raw s3://your-ami-bucket/
# Create import task
aws ec2 import-image \
--description "NixOS AMI" \
--disk-containers "Format=RAW,UserBucket={S3Bucket=your-ami-bucket,S3Key=nixos-amazon-image-XXXX.raw}"
# Check import status
aws ec2 describe-import-image-tasks --import-task-ids import-ami-XXXX# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
nixos-generators = {
url = "github:nix-community/nixos-generators";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, nixos-generators, flake-utils }:
flake-utils.lib.eachSystem [ "x86_64-linux" "aarch64-linux" ] (system:
let
pkgs = import nixpkgs { inherit system; };
makeAmazonAMI = { hostname, modules ? [] }:
nixos-generators.nixosGenerate {
inherit system;
format = "amazon";
modules = [
({ modulesPath, ... }: {
imports = [ "${modulesPath}/virtualisation/amazon-image.nix" ];
virtualisation.diskSize = 16 * 1024;
})
({ pkgs, ... }: {
nix.settings.experimental-features = [ "nix-command" "flakes" ];
environment.systemPackages = with pkgs; [ git vim ];
services.openssh.enable = true;
services.openssh.settings.PasswordAuthentication = false;
users.users.admin = {
isNormalUser = true;
extraGroups = [ "wheel" ];
openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAA..." ];
};
security.sudo.wheelNeedsPassword = false;
})
] ++ modules;
};
in
{
packages = {
default = makeAmazonAMI { hostname = "nixos-server"; };
web-server = makeAmazonAMI {
hostname = "web-server";
modules = [
({ pkgs, ... }: {
services.nginx.enable = true;
networking.firewall.allowedTCPPorts = [ 80 443 ];
})
];
};
};
}
);
}For automatic configuration updates from a Git repository:
# flake-bootstrapper.nix
{ config, lib, pkgs, ... }:
{
options.services.flake-bootstrapper = {
enable = lib.mkEnableOption "Enable flake-bootstrapper";
githubRepo = lib.mkOption {
type = lib.types.str;
description = "GitHub repository URL";
};
hostname = lib.mkOption {
type = lib.types.str;
description = "Hostname for comin";
};
};
config = lib.mkIf config.services.flake-bootstrapper.enable {
services.comin = {
enable = true;
hostname = config.services.flake-bootstrapper.hostname;
remotes = [{
name = "origin";
url = config.services.flake-bootstrapper.githubRepo;
branches.main.name = "master";
poller.period = 60; # Check every 60 seconds
}];
};
};
}- nixos-generators handles the heavy lifting of creating bootable AMI images
- The
amazon-image.nixmodule from nixpkgs provides EC2-specific configuration - Build produces a raw disk image that must be imported via AWS VM Import/Export
- Modular design allows easy composition of different server configurations
- Optional GitOps integration enables automatic updates without rebuilding AMIs