Cloud Provider Configuration
Cloud Provider Configuration
Cloud consoles are mutable. The boring default is: NixOS images from nixos-generators (or official AMIs), cloud-init only for SSH keys at first boot, then the flake owns the instance.
Mental model
| Piece | Owner |
|---|---|
| AMI / qcow / gce image | nixos-generators on 26.05, or official NixOS AMIs |
| Instance size, disk, VPC | OpenTofu withPlugins |
| OS config | nixosConfigurations in the same repo |
| First-boot SSH | cloud-init / metadata once |
Do not bake AWS keys into the image. Do not grow user_data into a second configuration.nix. After first boot, Colmena / nixos-rebuild --flake takes over.
The NixOS firewall and the cloud security group must tell the same story. 0.0.0.0/0 on 22 “for debug” is the story of a scanner.
Worked examples
Case 1: Official AMI lookup (OpenTofu)
Save as ami.tf:
# ami.tf
data "aws_ami" "nixos" {
owners = ["427812963091"]
most_recent = true
filter {
name = "name"
values = ["nixos/26.05*"]
}
filter {
name = "architecture"
values = ["x86_64"]
}
}
Owner ID is the NixOS AMI account as documented on nixos.org/download. Confirm it when you copy. most_recent plus a name glob is a moving target — pin a specific AMI id once you have tested it:
# ami-pin.tf
variable "nixos_ami" {
type = string
default = "ami-xxxxxxxxxxxxxxxxx"
}
Case 2: Image you built
nix build .#amazon
ls -l result
# upload / AMI import per current AWS docs for the VHDThe generator produces the artifact. This chapter only consumes the AMI id in tofu. Do not hand-edit an AMI from 24.11 after the flake moved to 26.05.
Case 3: user-data is SSH keys, not packages
Save as userdata.tf:
# userdata.tf
resource "aws_instance" "app_01" {
ami = var.nixos_ami
instance_type = "t3.medium"
user_data = <<-EOT
#cloud-config
users:
- name: deskadmin
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI… deskadmin@ops-01
EOT
tags = {
Name = "desk-app-01"
Flake = "git.desk.internal/platform/desk"
HostKey = "app-01"
}
}
After first boot:
ssh deskadmin@203.0.113.11
sudo nixos-rebuild switch --flake git+ssh://git.desk.internal/platform/desk#app-01Do not keep adding packages to user_data. They vanish on the next rebuild anyway if the flake does not declare them.
Case 4: Security group matches the firewall
Save as sg.tf:
# sg.tf
resource "aws_security_group" "gw" {
name = "desk-gw"
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 51820
to_port = 51820
protocol = "udp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["203.0.113.0/24"] # office VPN, not the world
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
On the host, networking.firewall.allowedTCPPorts = [ 443 ]; and allowedUDPPorts = [ 51820 ];. If the SG is wider than NixOS, the SG is the bug. If NixOS is wider than the SG, the module is the bug.
The trap
The trap is an AMI from 24.11 still in tofu after the flake moved to 26.05. The instance boots old software, then the first rebuild is a surprise kernel. Pin AMI name nixos/26.05* or a specific AMI id you tested — and bump it on purpose.
The boring rule
- Images: 26.05 generator or official 26.05 AMI, then pin the id.
- tofu owns cloud objects. Flake owns the OS.
- user-data: SSH keys only. Then Colmena.
- SG + NixOS firewall agree. No
0.0.0.0/0on 22. - No cloud keys in the image.
Try this
aws ec2 describe-images --owners 427812963091 --filters Name=name,Values=nixos/26.05*(if you have AWS) and record an AMI id.- Compare that id to what tofu would pick with
most_recent. - List ports in the SG vs
networking.firewall.allowedTCPPortsfor one host — they should match. - Boot from user-data keys, then rebuild from the flake; confirm a package you did not put in user-data is present only because the flake says so.