4. Getting Started
One task, three ways. Create a workspace, add a compute cluster that scales to zero, submit a tracked training job, and register the model it produces. Then delete everything.
This page is deliberately throwaway. Nothing here is production-shaped — no private endpoints, no user-assigned identity, no remote state, no pinned versions. That is Deployment. The point is to see the objects move once.
[Image Prompt: 2D minimalistic diagram comparing three provisioning paths, Azure Portal, Azure CLI, and Terraform, converging on the same Azure Machine Learning workspace containing one compute cluster and one registered model, flat design, clean vector art style, white background]
Before you start
An Azure subscription where you can create resources and assign roles.
Azure CLI, with the v2 ML extension:
az extension add -n ml(oraz extension update -n ml).Check quota first. This is the step people skip and regret:
az ml compute list-usage --location eastus -o tableIf the
Standard_DSfamily shows zero available vCPUs, nothing below will run and no amount of YAML will fix it. Request quota, or pick a region where you have some.A throwaway resource group, created first, so teardown is one command:
export RG=rg-aml-demo export LOC=eastus export WS=aml-demo-$RANDOM az group create -n $RG -l $LOC
Path 1 — Portal
Blade names, not a click chain, because portal navigation rots fast.
- Azure Machine Learning → Create → fill in resource group, workspace name, and region. Leave the storage account, Key Vault, Application Insights, and container registry as auto-created — this is the throwaway path; in real work you name these yourself.
- Once deployed, Launch studio. You are now at
ml.azure.com, on the workspace's data plane. - In the studio: Compute → Compute clusters → New. Pick a small CPU size, set minimum nodes = 0, maximum nodes = 2, idle seconds before scale down = 120.
- Jobs → Create (or Notebooks if you'd rather run interactively). Point it at a command, an environment, and the cluster you just made.
- Models shows anything you register; Endpoints is where a deployment would go.
Two things worth noticing while you're here. The Compute instances tab is not the clusters tab — an instance is a personal VM billed while it runs, and if you create one, set idle shutdown now. And the workspace's Overview blade lists the four dependent resources; open the resource group and confirm you now own five things, not one.
Path 2 — Azure CLI (az ml)
Everything below is copy-pasteable and assumes the environment variables from above.
Create the workspace
az ml workspace create \
--name $WS \
--resource-group $RG \
--location $LOC
This auto-creates the storage account, Key Vault, and Application Insights with generated names. Fine for a demo; see Deployment for how to bring your own.
Make it the default so later commands are shorter:
az configure --defaults group=$RG workspace=$WS
Create a compute cluster that scales to zero
az ml compute create \
--name cpu-cluster \
--type AmlCompute \
--size Standard_DS3_v2 \
--min-instances 0 \
--max-instances 2 \
--idle-time-before-scale-down 120
--min-instances 0 is the whole point. With it, an idle cluster costs nothing.
A training script
src/train.py — note that it logs with MLflow, not an Azure SDK:
import argparse, mlflow
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
p = argparse.ArgumentParser()
p.add_argument("--n-estimators", type=int, default=100)
args = p.parse_args()
X, y = load_iris(return_X_y=True)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=42)
mlflow.sklearn.autolog() # logs params, metrics, and the model
model = RandomForestClassifier(n_estimators=args.n_estimators).fit(X_tr, y_tr)
mlflow.log_metric("test_accuracy", model.score(X_te, y_te))
mlflow.sklearn.autolog() registers the model artifact in MLflow format, which is what makes the
no-code deployment path possible later.
Submit it as a job
job.yml:
$schema: https://azuremlschemas.azureedge.net/latest/commandJob.schema.json
code: ./src
command: python train.py --n-estimators ${{inputs.n_estimators}}
inputs:
n_estimators: 200
environment: azureml://registries/azureml/environments/sklearn-1.5/labels/latest
compute: azureml:cpu-cluster
display_name: iris-rf
experiment_name: iris-demo
az ml job create -f job.yml --web
--web opens the run in the studio. The first submission is slow — code snapshot upload, then image
pull, then node allocation — and that has nothing to do with your training loop. See
Architecture.
The environment line uses a curated environment from the shared azureml registry. Note it's
pinned to labels/latest, which is fine here and wrong in production.
Register the model
Grab the job name from the previous command's output (or az ml job list -o table), then:
export JOB=<job-name-from-output>
az ml model create \
--name iris-rf \
--version 1 \
--type mlflow_model \
--path azureml://jobs/$JOB/outputs/artifacts/model
az ml model list -o table
You now have a versioned artifact whose lineage points back at the job that made it, which points at the code snapshot, the environment, and the metrics. That chain is the product.
Optional — deploy it and score once
Only if you want to see the serving path. This costs money per hour from the moment it succeeds.
az ml online-endpoint create --name iris-ep-$RANDOM --auth-mode key
# use the name you just created:
export EP=<endpoint-name>
az ml online-deployment create \
--endpoint-name $EP \
--name blue \
--model azureml:iris-rf:1 \
--instance-type Standard_DS3_v2 \
--instance-count 1 \
--all-traffic
No scoring script and no environment: an mlflow_model carries enough metadata for Azure ML to generate
both. Then:
echo '{"input_data": {"columns":[0,1,2,3],"data":[[5.1,3.5,1.4,0.2]]}}' > sample.json
az ml online-endpoint invoke --name $EP --request-file sample.json
Delete it as soon as you've seen it work:
az ml online-endpoint delete --name $EP --yes
Path 3 — Terraform (minimal)
Enough to create the workspace, its dependencies, and the cluster. Not a module, not parameterised, no remote state — Deployment does all of that properly.
terraform {
required_providers {
azurerm = { source = "hashicorp/azurerm", version = "~> 4.0" }
}
}
provider "azurerm" {
features {
# Azure ML workspaces soft-delete. Without this, `terraform destroy`
# followed by re-apply fails on a name that is "gone" but not purged.
machine_learning {
purge_soft_deleted_workspace_on_destroy = true
}
key_vault {
purge_soft_delete_on_destroy = true
}
}
}
resource "azurerm_resource_group" "demo" {
name = "rg-aml-demo-tf"
location = "eastus"
}
resource "azurerm_storage_account" "demo" {
name = "stamldemo${random_string.suffix.result}"
resource_group_name = azurerm_resource_group.demo.name
location = azurerm_resource_group.demo.location
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_key_vault" "demo" {
name = "kv-amldemo-${random_string.suffix.result}"
resource_group_name = azurerm_resource_group.demo.name
location = azurerm_resource_group.demo.location
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
purge_protection_enabled = false # demo only — see Deployment
}
resource "azurerm_application_insights" "demo" {
name = "appi-amldemo"
resource_group_name = azurerm_resource_group.demo.name
location = azurerm_resource_group.demo.location
application_type = "web"
}
resource "azurerm_machine_learning_workspace" "demo" {
name = "aml-demo-tf"
resource_group_name = azurerm_resource_group.demo.name
location = azurerm_resource_group.demo.location
storage_account_id = azurerm_storage_account.demo.id
key_vault_id = azurerm_key_vault.demo.id
application_insights_id = azurerm_application_insights.demo.id
identity { type = "SystemAssigned" }
}
resource "azurerm_machine_learning_compute_cluster" "cpu" {
name = "cpu-cluster"
location = azurerm_resource_group.demo.location
machine_learning_workspace_id = azurerm_machine_learning_workspace.demo.id
vm_priority = "Dedicated"
vm_size = "Standard_DS3_v2"
scale_settings {
min_node_count = 0
max_node_count = 2
scale_down_nodes_after_idle_duration = "PT2M"
}
}
data "azurerm_client_config" "current" {}
resource "random_string" "suffix" {
length = 6
special = false
upper = false
}
terraform init && terraform apply
Two things to notice, because they generalise. First, the features {} block is doing real work:
Azure ML workspaces and Key Vaults both soft-delete, and without those flags a destroy-then-apply cycle
fails on names that appear free and aren't. Second, there is no azurerm resource for jobs, models,
environments, or online deployments. Terraform builds the container; the assets inside it are
data-plane objects created by az ml or azapi. That division is the central design decision in
Deployment.
Teardown
Do this now, not later.
az group delete --name $RG --yes --no-wait
For the Terraform path:
terraform destroy
What deletion does not do, and this is the trap that generates duplicate resources next week:
The workspace soft-deletes. Its name is reserved for a retention period. Recreating with the same name fails until you recover it or purge it. ⚠️ Retention period and whether soft delete is on by default vary — verify against current Azure docs. Check with:
az ml workspace list-deleted -o table az ml workspace purge --name $WS --resource-group $RG --location $LOCKey Vault soft-deletes too, and if purge protection is on, it cannot be purged early — the name is gone for the retention period no matter what you do. That is why the demo above sets
purge_protection_enabled = falseand production sets it totrue.Deleting the workspace alone leaves storage, ACR, App Insights, and Key Vault running and billing. Deleting the resource group is what actually stops the meter, which is why we made one.
What to read next
You have seen the objects. Deployment turns this into something you would put a change ticket against — a parameterised module, remote state, user-assigned identity, blue/green endpoints, and a pipeline that authenticates without a secret.
Next: Deployment →
← Back to the Azure Machine Learning overview · ← Previous: Architecture