This Learning Path uses Terraform Cloud to automate instantiation of Arm instances. Reader may wish to also see:

Before you begin

You should have the prerequisite tools installed before starting the Learning Path.

Any computer which has the required tools installed can be used for this section. The computer can be your desktop, laptop, or a virtual machine with the required tools.

You will need an Azure portal account to complete this Learning Path. Create an account if you don’t have one.

Before you begin, you will also need:

  • Login to the Azure CLI
  • An SSH key pair

The instructions to login to the Azure CLI and create these keys are below.

Generate an SSH key-pair

Generate an SSH key-pair (public key, private key) using ssh-keygen to use for Arm VMs access. To generate the key-pair, follow this guide .


If you already have an SSH key-pair present in the ~/.ssh directory, you can skip this step.

Acquire Azure Access Credentials

The installation of Terraform on your desktop or laptop needs to communicate with Azure. Thus, Terraform needs to be authenticated.

For Azure authentication, follow this guide .

Image References

Before provisioning Terraform infrastructure, retrieve the required image details. For reference, please follow the Azure Documentation . The publisher, offer, sku and version details are needed to create a VM.

Get list of publishers for a specific location (eastus2):


            az vm image list-publishers --location eastus2 --output table

Get list of offers for required publishers (Canonical):


            az vm image list-offers --location eastus2 --publisher Canonical --output table

Get list of skus for required publisher and offer:


            az vm image list-skus --location eastus2 --publisher Canonical --offer 0001-com-ubuntu-server-focal --output table

Get image details for required publisher, offer and sku:


            az vm image list --location eastus2 --publisher Canonical --offer 0001-com-ubuntu-server-focal --sku 20_04-lts-arm64 --all --output table

Image list:


        ubuntu@ip-172-31-38-39:~$ az vm image list --location eastus2 --publisher Canonical --offer 0001-com-ubuntu-server-focal --sku 20_04-lts-arm64 --all --output table
Architecture    Offer                         Publisher    Sku              Urn                                                                     Version
--------------  ----------------------------  -----------  ---------------  ----------------------------------------------------------------------  ---------------
Arm64           0001-com-ubuntu-server-focal  Canonical    20_04-lts-arm64  Canonical:0001-com-ubuntu-server-focal:20_04-lts-arm64:20.04.202206150  20.04.202206150
Arm64           0001-com-ubuntu-server-focal  Canonical    20_04-lts-arm64  Canonical:0001-com-ubuntu-server-focal:20_04-lts-arm64:20.04.202206220  20.04.202206220
Arm64           0001-com-ubuntu-server-focal  Canonical    20_04-lts-arm64  Canonical:0001-com-ubuntu-server-focal:20_04-lts-arm64:20.04.202207050  20.04.202207050
Arm64           0001-com-ubuntu-server-focal  Canonical    20_04-lts-arm64  Canonical:0001-com-ubuntu-server-focal:20_04-lts-arm64:20.04.202207130  20.04.202207130
Arm64           0001-com-ubuntu-server-focal  Canonical    20_04-lts-arm64  Canonical:0001-com-ubuntu-server-focal:20_04-lts-arm64:20.04.202208100  20.04.202208100
Arm64           0001-com-ubuntu-server-focal  Canonical    20_04-lts-arm64  Canonical:0001-com-ubuntu-server-focal:20_04-lts-arm64:20.04.202209050  20.04.202209050
Arm64           0001-com-ubuntu-server-focal  Canonical    20_04-lts-arm64  Canonical:0001-com-ubuntu-server-focal:20_04-lts-arm64:20.04.202209200  20.04.202209200
Arm64           0001-com-ubuntu-server-focal  Canonical    20_04-lts-arm64  Canonical:0001-com-ubuntu-server-focal:20_04-lts-arm64:20.04.202210100  20.04.202210100
Arm64           0001-com-ubuntu-server-focal  Canonical    20_04-lts-arm64  Canonical:0001-com-ubuntu-server-focal:20_04-lts-arm64:20.04.202210140  20.04.202210140
Arm64           0001-com-ubuntu-server-focal  Canonical    20_04-lts-arm64  Canonical:0001-com-ubuntu-server-focal:20_04-lts-arm64:20.04.202210180  20.04.202210180
Arm64           0001-com-ubuntu-server-focal  Canonical    20_04-lts-arm64  Canonical:0001-com-ubuntu-server-focal:20_04-lts-arm64:20.04.202211151  20.04.202211151


Terraform infrastructure

Terraform files are created with a .tf extension.

Start by creating empty,, and files in your desired directory:




Tell Terraform which cloud provider to connect to, Azure for this example.

Add the code below to the file:


            terraform {
  required_version = ">=0.12"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>2.0"
    random = {
      source  = "hashicorp/random"
      version = "~>3.0"
    tls = {
      source  = "hashicorp/tls"
      version = "~>4.0"

provider "azurerm" {
  features {}


Define required variables to create a virtual machine.

Add the code below to the file:


            variable "resource_group_location" {
  default     = "eastus2"
  description = "Location of the resource group."

variable "resource_group_name_prefix" {
  default     = "rg"
  description = "Prefix of the resource group name that's combined with a random ID so name is unique in your Azure subscription."

Create required resources

Define required resources to create a virtual machine.

Add the code below to the file:


            resource "random_pet" "rg_name" {
  prefix = var.resource_group_name_prefix

resource "azurerm_resource_group" "rg" {
  location = var.resource_group_location
  name     =

# Create virtual network
resource "azurerm_virtual_network" "my_terraform_network" {
  name                = "myVnet"
  address_space       = [""]
  location            = azurerm_resource_group.rg.location
  resource_group_name =

# Create subnet
resource "azurerm_subnet" "my_terraform_subnet" {
  name                 = "mySubnet"
  resource_group_name  =
  virtual_network_name =
  address_prefixes     = [""]

# Create Public IPs
resource "azurerm_public_ip" "my_terraform_public_ip" {
  name                = "myPublicIP"
  location            = azurerm_resource_group.rg.location
  resource_group_name =
  allocation_method   = "Dynamic"

# Create Network Security Group and rule
resource "azurerm_network_security_group" "my_terraform_nsg" {
  name                = "myNetworkSecurityGroup"
  location            = azurerm_resource_group.rg.location
  resource_group_name =

  security_rule {
    name                       = "SSH"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"

# Create network interface
resource "azurerm_network_interface" "my_terraform_nic" {
  name                = "myNIC"
  location            = azurerm_resource_group.rg.location
  resource_group_name =

  ip_configuration {
    name                          = "my_nic_configuration"
    subnet_id                     =
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          =

# Connect the security group to the network interface
resource "azurerm_network_interface_security_group_association" "example" {
  network_interface_id      =
  network_security_group_id =

# Generate random text for a unique storage account name
resource "random_id" "random_id" {
  keepers = {
    # Generate a new ID only when a new resource group is defined
    resource_group =

  byte_length = 8

# Create storage account for boot diagnostics
resource "azurerm_storage_account" "my_storage_account" {
  name                     = "diag${random_id.random_id.hex}"
  location                 = azurerm_resource_group.rg.location
  resource_group_name      =
  account_tier             = "Standard"
  account_replication_type = "LRS"

# Create virtual machine
resource "azurerm_linux_virtual_machine" "my_terraform_vm" {
  name                  = "myVM"
  location              = azurerm_resource_group.rg.location
  resource_group_name   =
  network_interface_ids = []
  size                  = "Standard_D2ps_v5"

  os_disk {
    name                 = "myOsDisk"
    caching              = "ReadWrite"
    storage_account_type = "Premium_LRS"

  source_image_reference {
    publisher = "Canonical"
    offer     = "0001-com-ubuntu-server-focal"
    sku       = "20_04-lts-arm64"
    version   = "20.04.202209200"

  computer_name                   = "myvm"
  admin_username                  = "azureuser"
  disable_password_authentication = true

  admin_ssh_key {
    username   = "azureuser"
    public_key = file("~/.ssh/")

  boot_diagnostics {
    storage_account_uri = azurerm_storage_account.my_storage_account.primary_blob_endpoint


Get the Resource group name and Public IP to output after Terraform deployment.

Add the code below to the file:


            output "resource_group_name" {
  value =

output "public_ip_address" {
  value = azurerm_linux_virtual_machine.my_terraform_vm.public_ip_address

Terraform commands

Initialize Terraform

Run terraform init to initialize the Terraform deployment. This command downloads the Azure modules required to manage your Azure resources.


            terraform init

The output should be similar to:


        Initializing the backend...

Initializing provider plugins...

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.


Create a Terraform execution plan

Run terraform plan to create and preview an execution plan before applying it to your cloud infrastructure.


            terraform plan -out main.tfplan

A long output of resources to be created will be printed. The bottom of the output should be similar to:


        Plan: 11 to add, 0 to change, 0 to destroy.


Saved the plan to: main.tfplan


The terraform plan command is optional. You can directly run the terraform apply command, but it is always better to confirm the resources that will be created.

Apply a Terraform execution plan

Run terraform apply to apply the execution plan to your cloud infrastructure. The command below creates all required infrastructure.


            terraform apply main.tfplan

If prompted to confirm if you want to create Azure resources, answer yes.

The bottom of the output should be similar to:


        Apply complete! Resources: 11 added, 0 changed, 0 destroyed


public_ip_address = ""
resource_group_name = "rg-definite-mole"


Make note of the outputs to identify your instance. This is particularly useful when having multiple instances.

Verify created resources

Verify the resource group and virtual machine setup on the Azure Portal.

From the Azure Dashboard, go to Resource groups and ensure that your resource group has been created from Terraform.

The Resource group name should match your output above.

Image Alt Text:terraform1

Also from the Azure Dashboard, go to Virtual machines and ensure that your virtual machine has been created from Terraform.

The Resource group name and Public IP address of this virtual machine should match your output above.

Image Alt Text:terraform2

Use private key to SSH into Azure VM

Connect to your Azure VM with your preferred SSH client. You will be using the private key created through ssh-keygen , located at ~/.ssh/id_rsa.

Follow the connect instructions and commands mentioned in the Azure VM Connect section:

Image Alt Text:terraform3

For example, if using the default username azureuser:


            ssh -i <private_key> azureuser@<public_ip_address>

Replace <private_key> with the private key on your local machine and <public_ip_address> with the public IP of the target VM.

Once connected, the output should be similar to below:


        ubuntu@ip-172-31-17-218:~$ ssh -i id_rsa azureuser@
The authenticity of host * (" can't be established.
ED25519 key fingerprint is SHA256:9HqZbneGF wsn2L JrNu70a+S50s 1xvK7aCHImSDNOCH.
This key is not known by any other names

Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '' (ED25519) to the list of known hosts.
Welcome to Ubuntu 20.04.5 LTS (GNU/Linux 5.15.0-1020-azure aarch64)


You are now ready to use your instance.

Clean up resources

Run terraform destroy to delete all resources created through Terraform, including resource groups, virtual networks, and all other resources.


            terraform destroy

A long output of resources to destroy will be printed. If prompted to confirm if you want to destroy all resources, answer yes.

The bottom of the output should be similar to:


        Destroy complete! Resources: 11 destroyed.


Explore your instance

Run uname

Use the uname utility to verify that you are using an Arm-based server. For example:


            uname -m

will identify the host machine as aarch64.

Run hello world

Install the gcc compiler. If you are using Ubuntu, use the following commands. If not, refer to the GNU compiler install guide :


            sudo apt-get update
sudo apt install -y gcc

Using a text editor of your choice, create a file named hello.c with the contents below:


            #include <stdio.h>
int main(){
    printf("hello world\n");
    return 0;

Build and run the application:


            gcc hello.c -o hello

The output is shown below:


        hello world