Azure VM modules

The modules in the terraform/azure-vm/modules folder are implementing a Virtual Machine with the associated network setup and storage. This VM can be used to test/host the Docker services of this repo.

Prerequisites

Azure Storage Account for Remote State

Before using this Terraform configuration, create an Azure Storage Account to store the remote state.

IMPORTANT: The storage account name tfstateinfrandomanimal shown below is just an example. You must choose your own globally unique name.

Configuration Steps

  1. Choose a unique storage account name (3-24 characters, lowercase letters and numbers only)

  2. Update the configuration files:

    • Taskfile.yaml: Update the STORAGE_ACCOUNT_NAME variable
    • backend.tf: Update the storage_account_name value
  3. Create the storage account:

# Login to Azure (if not already logged in)
az login --use-device-code

# Set your custom storage account name
STORAGE_ACCOUNT_NAME="your-unique-name-here"  # CUSTOMIZE THIS

# Create Storage Account for Terraform state (skip if already exists)
az storage account create \
  --name $STORAGE_ACCOUNT_NAME \
  --resource-group HomeInfra \
  --location westeurope \
  --sku Standard_LRS \
  --encryption-services blob \
  --min-tls-version TLS1_2 \
  --allow-blob-public-access false

# Create container for state files
az storage container create \
  --name azure-vm-state \
  --account-name $STORAGE_ACCOUNT_NAME \
  --auth-mode login

# Verify creation
az storage blob list \
  --account-name $STORAGE_ACCOUNT_NAME \
  --container-name azure-vm-state \
  --output table \
  --auth-mode login
  1. Assign RBAC permissions (required for Azure AD authentication):
# Get your user object ID
CURRENT_USER=$(az ad signed-in-user show --query id -o tsv)

# Assign Storage Blob Data Contributor role
az role assignment create \
  --assignee "$CURRENT_USER" \
  --role "Storage Blob Data Contributor" \
  --scope "/subscriptions/$(az account show --query id -o tsv)/resourceGroups/HomeInfra/providers/Microsoft.Storage/storageAccounts/$STORAGE_ACCOUNT_NAME"

GitHub Actions OIDC Setup (Optional)

To enable automated Terraform plans on pull requests via GitHub Actions:

  1. Create Azure AD Application:
# Set your repository details
export GITHUB_ORG="bubacoder"
export GITHUB_REPO="infra"
export SUBSCRIPTION_ID=$(az account show --query id -o tsv)

# Create application and service principal
APP_ID=$(az ad app create \
  --display-name "github-actions-terraform-azure-vm" \
  --query appId -o tsv)
echo "Application ID: ${APP_ID}"

az ad sp create --id ${APP_ID}

# Get Object ID for federated credentials
OBJECT_ID=$(az ad app show --id ${APP_ID} --query id -o tsv)
echo "Object ID: ${OBJECT_ID}"

# Assign Contributor role
az role assignment create \
  --assignee ${APP_ID} \
  --role Contributor \
  --scope /subscriptions/${SUBSCRIPTION_ID}

Note: After the Key Vault is created (on first terraform apply), you need to grant the service principal access to manage secrets:

# Get the Key Vault name from terraform output
KEYVAULT_NAME=$(cd terraform/azure-vm && terraform output -raw key_vault_name)

# Get the service principal object ID
SP_OBJECT_ID=$(az ad sp show --id ${APP_ID} --query id -o tsv)

# Add access policy for Terraform to manage secrets
az keyvault set-policy \
  --name ${KEYVAULT_NAME} \
  --object-id ${SP_OBJECT_ID} \
  --secret-permissions get list set delete
  1. Create Federated Credentials:
# For pull requests
az ad app federated-credential create \
  --id ${OBJECT_ID} \
  --parameters '{
    "name": "github-pr",
    "issuer": "https://token.actions.githubusercontent.com",
    "subject": "repo:'"${GITHUB_ORG}/${GITHUB_REPO}"':pull_request",
    "audiences": ["api://AzureADTokenExchange"]
  }'

# For main branch (future use)
az ad app federated-credential create \
  --id ${OBJECT_ID} \
  --parameters '{
    "name": "github-main",
    "issuer": "https://token.actions.githubusercontent.com",
    "subject": "repo:'"${GITHUB_ORG}/${GITHUB_REPO}"':ref:refs/heads/main",
    "audiences": ["api://AzureADTokenExchange"]
  }'

# Verify credentials
az ad app federated-credential list --id ${OBJECT_ID}
  1. Configure GitHub Secrets (Settings > Secrets and variables > Actions):
# Display values for GitHub secrets
echo "=== GitHub Secrets ==="
echo "AZURE_CLIENT_ID: ${APP_ID}"
echo "AZURE_TENANT_ID: $(az account show --query tenantId -o tsv)"
echo "AZURE_SUBSCRIPTION_ID: ${SUBSCRIPTION_ID}"
  • Add AZURE_CLIENT_ID: Application ID from above
  • Add AZURE_TENANT_ID: Tenant ID from above
  • Add AZURE_SUBSCRIPTION_ID: Subscription ID from above

For more details, see the Azure documentation.

State Migration

If migrating from local state to Azure remote state, use the migration task:

# From repository root
task azure-vm:migrate-state

# Or from this directory
task migrate-state

This will backup your local state and migrate it to Azure Blob Storage.

Usage

  1. Login to Azure account (without browser access on device): az login --use-device-code
  2. See the file terraform/azure-vm/Taskfile.yaml for available task commands for deploying/connection/deleting the VM.
  3. Execute commands like task plan (in this folder) or task azure-vm:plan (anywhere within the repo).

Note: for the previous Makefile (which was replaced by Taskfile) see MR #168.

Cloud-init caveat: custom_data is a ForceNew attribute in the azurerm provider, meaning Terraform will destroy and recreate the VM if cloud-init templates change. The data disk is preserved thanks to lifecycle { prevent_destroy = true } in the storage module. To apply updated cloud-init content, run task azure-vm:destroy-vm followed by task azure-vm:apply.

Requirements

NameVersion
terraform>= 1.5
azurerm~> 5.0
random~> 3.0

Providers

NameVersion
random~> 3.0

Modules

NameSourceVersion
base./modules/basen/a
keyvault./modules/keyvaultn/a
storage./modules/storagen/a
vm./modules/vmn/a

Resources

NameType
random_string.keyvault_suffixresource

Inputs

NameDescriptionTypeDefaultRequired
admin_source_addressAllow connections (SSH, …) only from this IPstringn/ayes
admin_userName of the administrative user on the VMstring"azureuser"no
git_credentialsGit credentials for accessing the infrastructure repository. Will be written to ~/.git-credentialsstring""no
locationLocation of the resourcesstring"westeurope"no
repo_directoryName of the infrastructure repository directorystring"infra"no
repo_urlURL of the infrastructure repositorystringn/ayes
resourcegroupName of Resource Groupstring"HomeInfra"no
storage_disk_size_gbSize of the permanent disk in GBnumber10no
subscription_idAzure subscription ID (format: ‘00000000-xxxx-xxxx-xxxx-xxxxxxxxxxxx’)stringn/ayes
vm_domain_name_labelDNS name of the VM. The FQDN will be: <vm_domain_name_label>..cloudapp.azure.comstringn/ayes
vm_nameName, hostname of the VMstringn/ayes
vm_sizeSize of the VMstring"Standard_D2s_v5"no
vm_ubuntu_server_offerOffer of the VMstring"ubuntu-24_04-lts"no
vm_ubuntu_server_skuSKU of the VMstring"server"no

Outputs

NameDescription
key_vault_nameThe name of the Key Vault containing git credentials
vm_fqdnn/a
vm_idn/a
vm_public_ip_addressn/a
vm_public_key_fingerprint_sha256n/a
vm_tls_private_keyn/a