MOV

5. Provisioning a VM Using AZ CLI, Configuring Nginx, and Allowing HTTP Traffic

Overview

This exercise introduces creating a Virtual Machine (VM) with Azure CLI, using default SSH keys for secure authentication, installing Nginx, and configuring the network to allow HTTP traffic. You will work exclusively in the VSCode Integrated Terminal (Windows users: Make sure you use Git Bash).

Step 1: Set Up the Environment

  1. Open VSCode and launch the Integrated Terminal:

    • On Windows, use Git Bash inside VSCode.
    • On Mac or Linux, use the default terminal.
  2. Verify that Azure CLI is installed by running:

    »

5. Securely Manage Servers Behind a Bastion Host

In this tutorial, you’ll learn how to copy files from your local laptop to a web server running in Azure.
We’ll cover two scenarios:

  1. Direct access (server has a public IP).
  2. Indirect access via a bastion host (server only has a private IP).

We’ll also use a staging folder (/tmp/app) on the server to keep things clean and make updates easier.


1. Local Project Setup

On your laptop, create a folder named app/ with three files:

»

6. Automating VM Creation, Nginx Installation, and Port Configuration Using a Bash Script

Overview

In this exercise, you will create a Bash script that automates the provisioning of a resource group, a VM, and the opening of port 80. You will also use a custom data file to automatically install and configure Nginx on the VM during provisioning. This exercise enables a “one-click” solution for deploying a web server.

Step 1: Prepare the Custom Data File

  1. Create a file named custom_data_nginx.sh (You can use VSCode)

    »

6. Use A Reverse Proxy In Front of the App

Introduction

This tutorial is designed for developers and system administrators looking to configure Nginx as a reverse proxy in front of an application server on Azure. Both servers will reside within the same virtual network.

Prerequisites

  • An active Azure account. Sign up here if you don’t have one.
  • Familiarity with Azure portal, cloud networking, and SSH.
  • Basic knowledge of Nginx.

In order to “set the table” so that we can verify that the reverse proxy works as expected, let’s create two VMs - one Application Server and one Reverse Proxy. These two servers should be connected to the same subnet in this example.

»

Exercise 7. Exporting a Template from Azure Portal

Goal: See your week‑one Ubuntu VM and its network as Infrastructure as Code by exporting the deployment to an ARM template. This will give you a concrete reference before we author templates ourselves.

Estimated time: 15–25 minutes


Learning outcomes

  • Understand how Azure resources are represented in ARM.
  • Identify parameters, variables, resources, and dependsOn.
  • Map resource types to our diagram: VNet/Subnet, NSG, Public IP, NIC, Ubuntu VM.

Prerequisites

  • You already created the VM in the Azure Portal (from Exercise 1/2/3/5/6).
  • Access to the Resource Group that holds the VM and its networking.

What you’ll produce

  • A downloaded ZIP containing template.json and parameters.json (generated by the portal).
  • A short note with the resource types you found and how they relate.

Steps

  1. Open your Resource Group

    »

Exercise 8. Minimal ARM Template: Ubuntu VM + Network

Goal: Author the smallest viable ARM template that deploys an Ubuntu VM with a minimal network (VNet/Subnet, NSG, Public IP, NIC). Preview with what-if, deploy, and capture the public IP as an output.

Estimated time: 40–60 minutes


Learning outcomes

  • Structure an ARM template: parameters, variables, resources, outputs.
  • Deploy to a resource group with az deployment group and use what-if.
  • Wire up NSG → NIC → VM and output the Public IP.

Prerequisites

  • Azure CLI, VS Code.
  • A resource group (or create one).
  • Your SSH public key (~/.ssh/id_rsa.pub or id_ed25519.pub).

What you’ll produce

  • main.json — minimal template.
  • parameters.dev.json — parameter file for your environment.
  • A successful deployment and the public IP printed as an output.

Steps

  1. Scaffold files
    Create a new folder and two files: main.json and parameters.dev.json.

main.json (paste & save):

»

Exercise 9. From ARM to Bicep: Ubuntu VM + Network

Goal: Rebuild Exercise 8 using Bicep instead of ARM JSON. Produce a clean main.bicep and a .bicepparam file, preview with what-if, then deploy.

Estimated time: 40–60 minutes


Learning outcomes

  • Author resources in Bicep and understand how it compiles to ARM.
  • Use .bicepparam files for environment-specific values.
  • Output the Public IP and verify SSH/Nginx like in Exercise 8.

Prerequisites

  • Exercise 8 completed (or read through).
  • Azure CLI + Bicep installed (az bicep install or standalone).
  • SSH public key available.

What you’ll produce

  • main.bicep — minimal VM + network in Bicep.
  • dev.bicepparam — parameters for your environment.
  • A successful deployment and the public IP printed as an output.

Steps

  1. Create a bicep template

Create main.bicep and paste:

»

Exercise 10. Nginx via Cloud-init (bash) and Custom Script Extension

Goal: Install Nginx on an Ubuntu VM using two approaches and compare them:

  1. Cloud-init (customData) — runs at VM creation, script as a separate bash file
  2. Custom Script extension — can run on an existing VM, script as a separate bash file

Estimated time: 45–60 minutes


Learning outcomes

  • Pass a bash script to cloud-init via customData and loadTextContent().
  • Use the Custom Script extension to execute a bash script on an existing VM.
  • Know when to choose cloud-init vs extension.

Prerequisites

  • Exercise 9 (Bicep) or similar main.bicep + dev.bicepparam in place.
  • Azure CLI and Bicep installed.
  • An Ubuntu VM template (from earlier exercises).

What you’ll produce

  • cloud-init.sh — bash script for cloud-init.
  • install-nginx.sh — bash script for the Custom Script extension.
  • Updated main.bicep and dev.bicepparam that wire both methods.

Part A — Cloud-init (runs on new VM creation)

  1. Create cloud-init_nginx.sh (keep it minimal; no HTML changes):
#!/bin/bash
apt-get update
apt-get install -y nginx
  1. Update main.bicep — add parameter and pass it to osProfile.customData:
@description('User-data passed to cloud-init; start the script with #!/bin/bash')
param cloudInitContent string = ''

// in the VM resource:
osProfile: {
  computerName: vmName
  adminUsername: adminUsername
  customData: base64(cloudInitContent)
  linuxConfiguration: {
    disablePasswordAuthentication: true
    ssh: {
      publicKeys: [{ path: '/home/${adminUsername}/.ssh/authorized_keys', keyData: adminPublicKey }]
    }
  }
}
  1. Update dev.bicepparam — load the bash script from file:
param cloudInitContent = loadTextContent('./cloud-init_nginx.sh')
  1. Deploy (requires a new VM)
    Cloud-init is only read during creation:
az deployment group create -g rg-bicep-demo2 -f main.bicep -p dev.bicepparam adminPublicKey=@~/.ssh/id_rsa.pub

az deployment group show -g rg-bicep-demo2 -n main --query properties.outputs.publicIpAddress.value -o tsv

Verification

  • Browse to your public IP and verify Nginx welcome page

Part B — Custom Script Extension (works on existing VM)

  1. Create cse_deploy_webpage.sh (sh; minimal):
#!/bin/sh
set -eu # Exit on error, treat unset variables as an error

# Create the web root even if nginx hasn’t created it yet
install -d -m 0755 /var/www/html

# Write the web page
cat >/var/www/html/index.html <<'HTML'
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><title>Azure VM — Nginx</title></head>
<body><h1>It works 🎉</h1><p>Deployed via Azure <strong>Custom Script Extension</strong>.</p></body>
</html>
HTML
  1. Add parameter in dev.bicepparam for script content:
param customScriptContent = loadTextContent('./cse_deploy_webpage.sh')
  1. Add extension resource to main.bicep (after VM resource):
@description('Custom Script content to execute on the VM (bash)')
param customScriptContent string = ''

resource vmExt 'Microsoft.Compute/virtualMachines/extensions@2024-07-01' = {
  name: 'deploy-webpage'
  parent: vm
  location: location
  properties: {
    publisher: 'Microsoft.Azure.Extensions'
    type: 'CustomScript'
    typeHandlerVersion: '2.1'
    autoUpgradeMinorVersion: true
    settings: {
      // Inline script executed by /bin/sh (extension decodes Base64 and runs it as script.sh)
      script: base64(customScriptContent)
    }
  }
}
  1. Deploy (can be applied to an existing VM)
    This will add/update only the extension:
az deployment group what-if -g rg-bicep-demo2 -f main.bicep -p dev.bicepparam adminPublicKey=@~/.ssh/id_rsa.pub
az deployment group create -g rg-bicep-demo2 -f main.bicep -p dev.bicepparam adminPublicKey=@~/.ssh/id_rsa.pub

Verify

curl <public-ip>

You can decode the string from what-if like this

»