Skip to content

Convert to Bicep templates and update Ubuntu image support - #5

Merged
dmauser merged 18 commits into
dmauser:masterfrom
cloudchristoph:feat/ubuntu-2404-support
Aug 13, 2026
Merged

Convert to Bicep templates and update Ubuntu image support#5
dmauser merged 18 commits into
dmauser:masterfrom
cloudchristoph:feat/ubuntu-2404-support

Conversation

@cloudchristoph

@cloudchristoph cloudchristoph commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

This pull request introduces two new Bicep templates (LinuxRouter.bicep and LinuxRouter-newsubnet.bicep) for deploying a Linux router VM in Azure, and updates their corresponding ARM templates (LinuxRouter.json and LinuxRouter-newsubnet.json). The main improvements include support for Ubuntu 22.04 and 24.04, parameterization of key deployment options, modernization of API versions, and enhanced flexibility for network and public IP configuration.

The most important changes are:

Template Modernization and Flexibility

  • Added new Bicep templates (LinuxRouter.bicep, LinuxRouter-newsubnet.bicep) with improved parameterization for VM size, OS version, disk type, admin credentials, and public IP deployment, making deployments more flexible and maintainable. [1] [2]
  • Updated API versions for all resources (VM, NIC, NSG, subnet, public IP, extensions) to the latest available, ensuring compatibility with current Azure features and best practices. [1] [2] [3] [4]

Operating System and Disk Options

  • Introduced the ability to select Ubuntu 22.04 or 24.04 as the OS version for the VM, and parameterized disk storage type, providing greater control over the VM configuration. [1] [2] [3] [4]

Network and Security Enhancements

  • Improved network configuration by allowing the deployment of a new subnet (with associated NSG) or use of an existing subnet, and added a security rule to allow RFC1918 traffic. [1] [2] [3]
  • Made public IP address deployment optional via the deployPublicIpAddress parameter, with conditional resource creation and NIC attachment logic. [1] [2] [3] [4]

Template Metadata and Minor Fixes

  • Added Bicep generator metadata to the ARM templates for traceability. [1] [2]
  • Corrected minor typos in parameter descriptions (e.g., "Linux Router Manchine Name" to "Linux Router Machine Name"). [1] [2]

Fixed issues

Fixes #3 — Ubuntu 18.04 LTS is out of support: Canonical has meanwhile removed the 18.04 images from the Azure Marketplace entirely, so the templates were no longer deployable. They now offer Ubuntu 22.04 (jammy, Gen2) and 24.04 (noble) via the new osVersion parameter, defaulting to 24.04 LTS.

Fixes #1 — Deployment of Linux VM fails, package iptables-persistent cannot be found: linuxrouter.sh now runs apt-get update before installing packages (which also ensures /etc/iptables exists once the package is installed). The script was verified end-to-end on Ubuntu 22.04 and 24.04: package installation, NAT rules, and persistence in /etc/iptables/rules.v4.

Brings the stalled Ubuntu/Bicep work (Bicep sources, osVersion
parameter, optional public IP) into the Ubuntu support branch.
Resolves the linuxrouter.sh conflict by dropping the unfinished
raw-nft path and unifying on iptables, which is nftables-backed
on Ubuntu 20.04 and later.
Sync with upstream; brings linuxrouterv2.sh.
The UbuntuLTS alias was removed from the Azure CLI. Also match the
Ubuntu image offer case-insensitively in deploylinuxnvabgpnp.azcli,
since new deployments use the 0001-com-ubuntu-server-jammy offer.
- NSG rule used a comma-separated sourceAddressPrefix string, which is
  invalid; use sourceAddressPrefixes. Protocol * instead of TCP so the
  router also handles UDP/ICMP traffic.
- Subnet was created as '<name>-vnet' instead of the given subnet name
- scriptUri default pointed to LinuxRouter.sh (wrong case)
Canonical no longer publishes Ubuntu 18.04 images in the Azure
Marketplace, so the templates were not deployable anymore. Both
templates now take an osVersion parameter offering 22.04 (jammy,
Gen2) and 24.04 (noble), defaulting to 24.04 LTS.

linuxrouter.sh needs no version branching: from Ubuntu 20.04 on,
iptables is backed by nftables (iptables-nft), so the same commands
work unchanged - verified on 22.04 and 24.04.
Basic SKU public IPs were retired in Azure (September 2025), which
made new deployments fail. Also bump the 2015-2022 resource API
versions to current ones, express the optional public IP as a proper
conditional object, name the OS disk explicitly in the newsubnet
template, and drop the completed roadmap item from the README.
@cloudchristoph
cloudchristoph marked this pull request as ready for review August 12, 2026 17:14
Copilot AI lite review requested due to automatic review settings August 12, 2026 17:14

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR modernizes the Linux router deployment assets by introducing Bicep sources (and regenerated ARM JSON) and updating Ubuntu image selection to support Ubuntu 22.04/24.04, along with more flexible networking/public IP options.

Changes:

  • Added new Bicep templates for Linux router deployments (existing subnet vs. new subnet) and regenerated the corresponding ARM templates.
  • Updated VM image selection to support Ubuntu 22.04 and 24.04, and refreshed API versions across resources.
  • Made public IP deployment optional and adjusted scripts/docs for newer Ubuntu images and tooling.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
README.md Updates Linux router template description to reflect Ubuntu 24.04 default + Bicep source-of-truth.
notes.txt Updates example az vm create image to Ubuntu 22.04.
linuxrouterv2.sh Removes stray trailing markdown backticks.
linuxrouter.sh Updates install steps for persistence packages and adds Ubuntu version context.
LinuxRouter.json Regenerated ARM template with new parameters (osVersion, optional public IP), updated API versions, and imageReference mapping.
LinuxRouter.bicep New Bicep template (existing subnet) with parameterization and Ubuntu 22.04/24.04 image selection.
LinuxRouter-newsubnet.json Regenerated ARM template (new subnet) with NSG/subnet creation, updated APIs, imageReference mapping, and optional public IP.
LinuxRouter-newsubnet.bicep New Bicep template (new subnet) adding NSG/subnet creation plus VM/NIC/PIP resources.
deploylinuxnvabgpnp.azcli Updates Ubuntu VM selection filter for applying a tooling extension.
deploylinuxnvabgp.azcli Updates az vm create image to Ubuntu 22.04.
deploylinuxnva.azcli Updates az vm create image to Ubuntu 22.04.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread LinuxRouter.bicep Outdated
@secure()
param adminPassword string

@description('Existing Virtual Nework Name')
Comment thread LinuxRouter-newsubnet.bicep Outdated
@secure()
param adminPassword string

@description('Existing Virtual Nework Name')
Comment on lines +47 to +49
var extensionName = 'CustomScript'
var nicName = '${virtualMachineName}-nic'
var publicIPAddressName = '${virtualMachineName}-PublicIP'
Comment on lines 109 to +112
"extensionName": "CustomScript",
"NIC": "[concat(parameters('virtualMachineName'),'-NIC')]",
"publicIPAddressName": "[concat(parameters('virtualMachineName'),'-PublicIP')]",
"subnet1Ref": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('existingVirtualNetworkName'), parameters('subnetName'))]",
"location": "[resourceGroup().location]"
"nicName": "[format('{0}-nic', parameters('virtualMachineName'))]",
"publicIPAddressName": "[format('{0}-PublicIP', parameters('virtualMachineName'))]",
"osVersionDefinitions": {
Comment thread LinuxRouter.bicep Outdated
Comment on lines +41 to +42
@description('Deploy Public IP Address')
param deployPublicIpAdress bool = true
Comment thread LinuxRouter-newsubnet.bicep Outdated
Comment on lines +44 to +45
@description('Deploy Public IP Address')
param deployPublicIpAdress bool = true
Comment thread linuxrouter.sh Outdated
Comment on lines 14 to 17
export DEBIAN_FRONTEND=noninteractive

echo "Updating repositories"
sudo apt-get update -y --fix-missing
Comment thread deploylinuxnvabgpnp.azcli Outdated
echo Installing tools for networking connectivity validation such as traceroute, tcptraceroute, iperf and others
nettoolsuri="https://raw.githubusercontent.com/dmauser/azure-vm-net-tools/main/script/nettools.sh"
for vm in `az vm list -g $rg --query "[?storageProfile.imageReference.offer=='UbuntuServer'].name" -o tsv`
for vm in `az vm list -g $rg --query "[?contains(storageProfile.imageReference.offer, 'ubuntu')].name" -o tsv`
- Fix 'Nework' typo in parameter descriptions
- Restore '-NIC' name casing in the newsubnet template so redeploys
  into existing resource groups keep the same NIC resource name
- Rename deployPublicIpAdress -> deployPublicIpAddress (new parameter
  in this PR, so no compatibility impact)
- linuxrouter.sh: drop sudo - the script already requires root and
  runs as root via the Custom Script Extension; this also makes
  DEBIAN_FRONTEND=noninteractive actually reach apt-get
  (sudo env_reset would strip it). Re-verified on 22.04/24.04.
- Match both 'ubuntu' and 'Ubuntu' image offers in the VM filter
Standard SKU public IPs block inbound Internet traffic until an NSG
allows it, while outbound stays available through the attached public
IP as an explicit outbound method. Also documents the outbound
requirements when deploying without a public IP, now that Azure has
retired default outbound access for new deployments.
@dmauser

dmauser commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Thanks for this — reviewed it in depth and validated it by actually deploying to Azure. This is good work and I'm merging it.

Beyond the Ubuntu 24.04 support, it quietly fixes several real latent bugs that were breaking the templates:

  • sourceAddressPrefix was being built as a comma-joined string, which isn't valid ARM — the NSG rule never actually worked.
  • The subnet was created as <name>-vnet.
  • The subnet had a backwards dependsOn pointing at the NIC.
  • scriptUri referenced LinuxRouter.sh with the wrong case, which breaks on the case-sensitive raw.githubusercontent path.

I also confirmed the checked-in ARM JSON is byte-identical to az bicep build output for both templates, and that reverting PR #2's sudo addition is correct — the Custom Script Extension already runs as root, and sudo's env_reset was stripping DEBIAN_FRONTEND=noninteractive.


One pre-existing bug worth folding in: a CSE / cloud-init race

While validating I deployed both templates. LinuxRouter-newsubnet.json on 24.04 succeeded, but LinuxRouter.json on 22.04 failed on the same run:

E: Unable to locate package netfilter-persistent
E: Unable to locate package iptables-persistent
/bin/sh: 1: cannot create /etc/iptables/rules.v4: Directory nonexistent

CSE exited 2. To be clear: this is not caused by your PRgit show master:linuxrouter.sh shows the same behaviour, it's just timing-dependent so it doesn't reproduce every time.

Root cause. The Custom Script Extension can start while cloud-init is still rewriting /etc/apt/sources.list from archive.ubuntu.com to the Azure mirror azure.archive.ubuntu.com. Apt's package indexes under /var/lib/apt/lists/ are keyed by mirror hostname, so once the rewrite lands, the indexes already on disk belong to the superseded mirror and apt knows zero packages.

Diagnostic signature on a failed VM:

$ apt-cache policy netfilter-persistent     # empty output
$ grep -r . /etc/apt/sources.list           # azure.archive.ubuntu.com
$ ls /var/lib/apt/lists/                    # archive.ubuntu.com_* files

Re-running the identical commands by hand after cloud-init status reported done worked every time.

Fix — guard the apt work at the top of linuxrouter.sh, right after export DEBIAN_FRONTEND=noninteractive:

# The Custom Script Extension can start before cloud-init has finished setting
# up the apt sources. Installing then fails with "Unable to locate package"
# because the package indexes on disk belong to the superseded mirror.
echo "Waiting for cloud-init to complete"
cloud-init status --wait >/dev/null 2>&1 || true

No deadlock risk: waagent runs extensions independently of cloud-init, so cloud-init never waits on the extension. The || true keeps it harmless on images where cloud-init isn't present.

I've already applied this to linuxrouter.sh and the other bootstrap scripts on my side and re-deployed the exact configuration that failed — it now succeeds. Since I'm merging this PR first, I'll carry that change in; no action needed from you unless you'd like to add it here.


Validation results

Everything below was run against a live subscription and has since been torn down.

Check Result
LinuxRouter-newsubnet.json @ Ubuntu 24.04 Succeeded
LinuxRouter.json @ Ubuntu 22.04 (with cloud-init guard) Succeeded
CSE provisioning state, both VMs Succeeded
NIC enableIPForwarding true
In-guest ip_forward / IPv6 forwarding / accept_redirects=0 correct
netfilter-persistent + iptables-persistent installed, service enabled yes
4 NAT POSTROUTING rules + /etc/iptables/rules.v4 persisted yes
Survives reboot yes
End-to-end A client VM with no public IP and a 0.0.0.0/0 UDR pointing at the NVA ran curl ifconfig.io and got back the NVA's public IP — proving forwarding and MASQUERADE both work

One heads-up on Standard SKU public IPs

Now that Basic SKU public IPs are retired, new public IPs are Standard, which is deny-by-default for inbound. LinuxRouter.bicep (the existing-subnet variant) doesn't create an NSG, so a one-click deploy lands a VM with no inbound SSH. That's out of scope for this PR — I'm adding an optional allowSshFromAddressPrefix parameter in a follow-up.

Thanks again for the contribution.

@dmauser
dmauser merged commit 6a43ef0 into dmauser:master Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ubuntu 18.04 LTS is out of support Deployment of Linux VM fails - Package iptables-persistent cannot be found.

3 participants