When choosing an Azure deployment tool, there are three basic weapons you can use to push your environment out to your Azure subscription. All of them have pros and cons, and all of them have faithfully devoted acolytes. Those approaches are…
Azure Resource Manager (ARM) Templates
ARM templates are essentially JSON payloads delivered to the Azure Resource Manager engine to tell it what to deploy with what values for what parameters. As new automation functionality becomes available, it shows up in ARM before it shows up anywhere else – it’s just JSON and doesn’t require an updated to az cli or powershell, so it’s easy to broadcast. They’re declarative and handy for controlling configuration drift. They’re logged in your Resource Group as Deployments, and the Deployments blade does a decent-but-not-great job of helping you locate failure points if something goes wrong with your template.
And something will. An ARM template has to pass validation twice – once for properly-formatted JSON, and again for properly-formatted ARM instructions. A mistyped ARM Template function will sail through JSONLint and still fail to deploy. You can get better (but still not ideal) error messages with powershell – Test-AzResourceGroupDeployment looks like it does what you want, but you have to dig into the return status to find anything useful. The base command just shows you the same failure you saw in the portal. To pry the details out, you have to do something like:
$whatisgoingonhere = Test-AzResourceGroupDeploy -ResourceGroup $RG -TemplateFile $File write-host $whatisgoingonhere.error.error
Azure DevOps has a handy Task type for ARM Deployment. It presupposes the existence of a Resource Group into which it should deploy, so if that’s not present you’ll need another step to build it. The task validates JSON but not ARM; if the JSON is valid it’ll ship it to the ARM engine, which will eventually evaluate it and throw BadRequest (it’s always BadRequest, even if the request is fine and it ends up being a naming conflict or something) if there’s an ARM syntax problem.
Azure Deployment Tool – az cli
Azure’s Command Line Interface, probably developed in the interest of attracting *nix operators/developers into Azure. It behaves an awful lot like a Linux command and if you’re cozy with cURL or other multi-switch Linux tools, you’ll feel right at home.
az cli solves some of the problems facing ARM templates. It’s much (much) thinner, more compact, and easier to read. By its nature, there’s no formatting check to run against it; az cli will never fail on the grounds that you added or subtracted a comma, or used a bracket when JSON expected a brace. You can compress hundreds of lines of ARM template into one az cli command, and when it fails (call it a hunch), it’ll tell you which line it choked on and what it thought it wanted vs what it actually got. Run on a Linux agent, it’s a bash script and that opens up decades of tools and skills in debugging bash.
It also introduces a couple of problems you don’t see with ARM templates. Run on Linux, it’s a bash script. Run on Windows, and it’s a cmd script, or a .bat file. Neither of those is ideal for debugging. az login is difficult to automate; it doesn’t accept credentials or anything else you can pass. It either opens a browser window for your credentials or suggests a URL into which you can past a machine code that it generates for you. AzDO solves that problem for you on az cli tasks, but if you’re debugging locally and then pushing to AzDO, this can be maddening – you have to have az login locally or you can’t run the rest of the commands, and you have to pull them out before you push to the repo or AzDO will stall on that line.
Azure PowerShell
Probably the intended holy grail of Azure infrastructure automation, it’s obvious that Microsoft expects you to use this wherever you can. It’s not as fast on new features as ARM templates, so sometimes you don’t have a choice. It’s more involved and requires more specific knowledge than az cli does.
It’s powershell, with all the tools and weapons you expect from powershell. It’ll run like any powershell script, the subscription login is comparatively painless, and it’s easy to debug (if you’re familiar with debugging powershell scripts there are no surprises for you here). There’s almost nothing to hate.
Almost. Microsoft periodically revises the Azure command set – it was Azure, then AzureRm, now just “Az”. That means revising perfectly good scripts, or trying to get AzureRm to co-exist with Az, an arrangement for which neither cares. Also, for reasons passing understanding, the AzDO Windows agents come loaded with the older AzureRm, which means your lovingly developed Az scripts will die alone and unloved if you try to deploy them with AzDO.
So which one’s best? I don’t know that there’s a good answer to that. Personally I’m not touching ARM again until or unless the “export template” feature starts behaving, and I’d use Powershell exclusively if AzDO agents supported the Az module. Since they don’t, I lean towards az cli on Ubuntu agents for the bulk of the work, and Powershell for bits that don’t require specific Azure functionality.
I can’t tell you what you’re most comfortable with, but I can suggest this: If you’re using any one of these approaches and banging your favorite head against a wall because it just doesn’t work, consider pulling back a bit and trying one of the other two. Application Gateways are nightmarishly complicated in ARM and pretty simple in az cli, for example. Launching a container from an Azure Container Registry is Herculean in az cli and might actually be impossible in Powershell, but does work well enough in ARM.
Originally published October 15, 2019. Information refreshed May 18, 2022.