Malte Krupa


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.

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:

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.


Privacy Policy | Imprint