Nerves on AWS - 2025-09-29
Assuming you know what Nerves is and how to change the configuration and the underlaying linux kernel, we just get started.
Prepare disk image
As a base, we’ll use the x86_64 system configuration.
Create a new project, make sure to set MIX_TARGET=x86_64
and also make sure
the following drivers are built into the linux kernel. Check the
documentation
for details on how to build a custom kernel.
CONFIG_NVME_CORE
CONFIG_BLK_DEV_NVME
CONFIG_PCI_MSI
CONFIG_ENA_ETHERNET
This will make storage (NVME) and networking (Elastic Network Adapters) work in AWS.
Then do the usual dance to build an img
file:
export MIX_TARGET="x86_64"
mix deps.get
mix firmware
qemu-img create -f raw disk.img 1G
fwup -d disk.img _build/x86_64_dev/nerves/images/<your_project>.fw
Create amazon machine image (AMI)
We need to do the following things:
- Create a S3 bucket (or use one that already exists)
- Upload the disk image to the bucket
- Create a role
vmimport
for theimport-snapshot
job - Create an EBS snapshot from the disk image
- Create an AMI from the EBS snapshot
Once everything is done you can use the AMI to create new VMs running nerves.
Preparation
Export bucket name and AWS region
export BUCKET_NAME="<bucket-name>"
export AWS_REGION="<region>"
Create a s3 bucket
aws s3 mb s3://$BUCKET_NAME
Upload disk image to s3 bucket
aws s3 cp disk.img s3://$BUCKET_NAME
Create vmimport role
We need to create a role which has the required permissions to import a disk
image as a snapshot or image. By default the aws ec2 import-snapshot
and aws ec2 import-image
commands use a role called vmimport
. You can change the name
on both commands using the --role-name
parameter.
For this guide we stick with the name vmimport
.
First we create a role with this trust policy which you should save to a file
called trust-policy.json
:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "vmie.amazonaws.com"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:Externalid": "vmimport"
}
}
}
]
}
Then create a role using this policy:
aws iam create-role --role-name vmimport --assume-role-policy-document file://trust-policy.json
Now we need to allow this role to do certain things. Save the following JSON to
a file called role-policy.json
. Make sure to replace YOUR_BUCKET_NAME
with
the name of your bucket.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::YOUR_BUCKET_NAME",
"arn:aws:s3:::YOUR_BUCKET_NAME/*"
]
},
{
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:GetObject",
"s3:ListBucket",
"s3:PutObject",
"s3:GetBucketAcl"
],
"Resource": [
"arn:aws:s3:::YOUR_BUCKET_NAME",
"arn:aws:s3:::YOUR_BUCKET_NAME/*"
]
},
{
"Effect": "Allow",
"Action": [
"ec2:ModifySnapshotAttribute",
"ec2:CopySnapshot",
"ec2:RegisterImage",
"ec2:Describe*"
],
"Resource": "*"
}
]
}
And finally we assign the policy to the role we already created.
aws iam put-role-policy --role-name vmimport --policy-name vmimport --policy-document file://role-policy.json
Create an EBS snapshot from the disk image
This command will import a snapshot from the disk.img
file. The vmimport
role will be used by default as shown in the documentation.
aws ec2 import-snapshot \
--description "nerves-$(date +"%Y%m%d%H%M%S") \
--disk-container '{
"Format": "RAW",
"UserBucket": {
"S3Bucket": "'${BUCKET_NAME}'",
"S3Key": "disk.img"
}
}'
Check the status of the import via this command:
aws ec2 describe-import-snapshot-tasks
The output might look like this:
{
"ImportSnapshotTasks": [
{
"Description": "nerves-20250929164312",
"ImportTaskId": "import-snap-bea76eae3f6140f2t",
"SnapshotTaskDetail": {
"DiskImageSize": 1073741824.0,
"Format": "RAW",
"Progress": "19",
"SnapshotId": "",
"Status": "active",
"StatusMessage": "downloading/converting",
"UserBucket": {
"S3Bucket": "mnesia-test",
"S3Key": "disk.img"
}
},
"Tags": []
}
]
}
Wait until the Status
field shows completed
and take note of the
SnapshotId
value which will appear once the job completed.
export SNAPSHOT_ID="snap-0538c2e855cb369b0"
Create an AMI from the EBS snapshot
aws ec2 register-image \
--name nerves-$(date +"%Y%m%d%H%M%S") \
--root-device-name /dev/sda1 \
--block-device-mappings DeviceName=/dev/sda1,Ebs={SnapshotId=${SNAPSHOT_ID}}
Now you’ll have an AMI from which you can boot nerves on a new EC2 instance.