Skip to content
Looking for Nebari Classic? Its docs have moved to classic.nebari.dev.

Microsoft Azure

Updated 10 min read

Microsoft Azure is Microsoft's public cloud. NKP deploys onto it as a managed Azure Kubernetes Service (AKS) cluster, so Azure runs and upgrades the Kubernetes control plane for you while nic (the Nebari Infrastructure Core CLI) provisions the cluster, its node pools, and the supporting infrastructure.

This guide walks you through a deployment from end to end: from an empty subscription to a running cluster you can manage and tear down. If you have deployed NKP on AWS, the flow will feel familiar; the differences are mostly in how you authenticate and in a few Azure-specific fields.

When nic deploy finishes, your team will have all standard NKP services plus:

  • A managed Kubernetes cluster ready for workloads (Azure AKS, with one System node pool and any number of User node pools).
  • Block storage through Azure managed disks (AKS's built-in disk StorageClasses; NKP's own databases use managed-csi).

You will need an Azure subscription you can deploy into. If you don't have one, create an Azure account.

  • Region: pick a region where AKS is available. The example uses eastus.
  • Role: the identity that runs nic deploy (your az login user or a service principal) needs Owner, or Contributor plus Role Based Access Control Administrator, on the subscription. nic creates role assignments for the cluster's managed identities, which Contributor alone cannot do.
  • Quotas: confirm your subscription has enough regional vCPU quota for the VM sizes in your node pools. The example below needs 12 Dv3-family vCPUs at its minimum size (52 at full scale, or 72 if you keep the starter config's worker pool), which is more than many new subscriptions allow, and nic does not check quota before it starts provisioning.
  • Cost: see Cost considerations for what bills.

nic needs one value from you directly, your subscription ID, and finds your identity through the standard Azure credential chain, DefaultAzureCredential. It tries environment-variable credentials first, then workload identity and managed identity, then the Azure CLI.

For most people the simplest path is the Azure CLI:

Terminal window
az login
az account set --subscription <subscription-id> # if you have more than one
az account show --query id --output tsv # prints your subscription ID

Follow the Install NIC guide to download and install the nic CLI for your platform.

See GitOps repository in the Prepare to deploy guide.

From inside your GitOps repo clone, download the template:

Terminal window
cd /path/to/your-gitops-repo
curl -o .env https://raw.githubusercontent.com/nebari-dev/nebari-infrastructure-core/v0.14.0/.env.example

Then fill in the GitOps tokens and your subscription ID. The template has a commented-out AZURE_SUBSCRIPTION_ID line under its Azure DNS heading; uncomment and set that one, or add the block below (not both). The template has no ARM_* lines, so add them yourself if you use a service principal. For .gitignore setup and GitOps token configuration, see Secrets and credentials in the Prepare to deploy guide.

Terminal window
# Azure
AZURE_SUBSCRIPTION_ID=00000000-0000-0000-0000-000000000000 # az account show --query id --output tsv
# Service-principal auth only (skip these if you use az login):
# AZURE_CLIENT_ID=...
# AZURE_TENANT_ID=...
# AZURE_CLIENT_SECRET=...
# ARM_CLIENT_ID=... # same value as AZURE_CLIENT_ID
# ARM_TENANT_ID=... # same value as AZURE_TENANT_ID
# ARM_CLIENT_SECRET=... # same value as AZURE_CLIENT_SECRET

nic deploy (including nic deploy --dry-run) fails before creating anything if AZURE_SUBSCRIPTION_ID is not set. You do not need to set ARM_SUBSCRIPTION_ID: nic passes the subscription ID through to OpenTofu for you.

A NKP deployment provisions several Azure services that bill from day one. Check the Azure pricing calculator for current rates in your region.

  • AKS control plane: the Free tier (the default) has no control-plane charge; setting sku_tier to Standard or Premium adds a per-cluster hourly fee for an uptime SLA.
  • Virtual machines in your node pools: per-hour cost for each running node.
  • Managed disks: an OS disk on each node (128 GB unless you set os_disk_size_gb) plus any managed-csi persistent volumes your workloads request.
  • Load balancer: the Standard Load Balancer that AKS creates for outbound traffic and ingress, and its public IP addresses.
  • Bandwidth: outbound data transfer.

Download the starter config from the nebari-infrastructure-core v0.14.0 release into the same directory as your .env, which is the directory you will deploy from:

Terminal window
curl -O https://raw.githubusercontent.com/nebari-dev/nebari-infrastructure-core/v0.14.0/examples/azure-config.yaml

At minimum, edit these fields:

project_name: my-cluster # letters, digits, and hyphens; see the naming note below
domain: nebari.example.com # a hostname you own
certificate:
type: letsencrypt
acme:
email: you@example.com # required for Let's Encrypt; renewal notices go here
repository:
existing: # the repository provider
url: "https://github.com/<your-org>/<your-gitops-repo>.git"
branch: main
path: clusters/my-cluster # subdirectory in the repo; conventionally matches project_name
auth:
token:
env: GIT_TOKEN # matches the GIT_TOKEN set in .env
cluster:
azure:
region: eastus # an AKS-supported region (required)
kubernetes_version: "1.35" # az aks get-versions --location <region>
node_groups: # at least one pool is required
system:
instance: Standard_D4_v3
min_nodes: 1
max_nodes: 3
mode: System # the pool that runs cluster services
user:
instance: Standard_D8_v3
min_nodes: 1
max_nodes: 5

The starter config authenticates to Git with an SSH key (auth.ssh.env: GIT_SSH_PRIVATE_KEY) instead. Either works as long as you set exactly one: replace its auth.ssh block with the auth.token block above, or keep it (with an SSH git@… URL) and set GIT_SSH_PRIVATE_KEY in .env (see the repository reference).

Every AKS cluster needs one System node pool to run cluster services. Set mode: System on the pool you want to play that role; pools without a mode are User pools. At most one pool may be System. If none is, the pool whose name sorts first alphabetically becomes the System pool, so set it explicitly rather than relying on the order of your pool names.

By default nic creates a resource group named <project_name>-rg. Set resource_group_name to deploy into one you already manage.

Networking uses Azure CNI Overlay, with the azure (default) or cilium dataplane set by network.dataplane, and the cluster runs under user-assigned managed identities that nic creates.

For the full schema (custom networking and existing VNets, network.dataplane, private clusters, authorized IP ranges, sku_tier, Node Auto Provisioning, per-pool disks, labels, taints, and zones), see the Azure provider configuration reference. If you enable private_cluster_enabled or authorized_ip_ranges, run nic from a network that can reach the cluster's API server: after provisioning, nic installs Argo CD and the foundational services through it.

Follow Deploy a cluster to deploy and verify. Expect the first deployment to take 20 minutes or more as AKS provisions the control plane and node pools, followed by ArgoCD syncing the foundational services.

On Azure, nic does not write a kubeconfig to a local path. Retrieve one with nic kubeconfig, as Deploy a cluster shows; it returns cluster-admin credentials directly from Azure, so you do not need the az CLI for kubectl to work.

Once the platform has converged, nic outputs prints its entry points, including the gateway's public IP address, which you need for DNS:

Terminal window
nic outputs -f <config-file> --wait

AKS gives the gateway a public IP address, so you point your domain at it with A records. Add a dns.cloudflare block to have nic create the records for you, or create them by hand with any DNS provider. See Cloudflare DNS for both paths.

See Keycloak authentication for first sign-in. Then add Software Packs from the pack catalog, as described in Deploying a pack.

To change something about a running cluster (scale a node pool, add a pool, change tags), edit your config and re-run the deploy commands as described in Update a cluster.

To upgrade, set kubernetes_version under cluster.azure and re-deploy:

cluster:
azure:
kubernetes_version: "1.36" # was "1.35"

nic passes the version straight through to AKS and does not sequence the upgrade itself; the rules come from AKS. AKS upgrades one minor version at a time and does not allow skipping versions or downgrading, so going from 1.34 to 1.36 means two re-deploys (1.34 → 1.35 → 1.36).

A re-deploy upgrades the AKS control plane only; your node pools keep their Kubernetes version until you upgrade them. After the re-deploy, upgrade the node pools to match, either all at once or one pool at a time:

Terminal window
# All node pools. The CLI warns that the cluster is already on this version;
# the node pools still upgrade.
az aks upgrade --resource-group <project_name>-rg --name <project_name>-aks \
--kubernetes-version 1.36
# Or a single pool
az aks nodepool upgrade --resource-group <project_name>-rg --cluster-name <project_name>-aks \
--name <pool> --kubernetes-version 1.36

Use your resource_group_name instead of <project_name>-rg if you set one. Both commands ask for confirmation unless you add --yes. Node pools can lag the control plane by at most three minor versions, so do not skip this step. For the control-plane-first workflow and node-pool upgrade options, see Upgrade the AKS cluster control plane.

See Upgrade Kubernetes version for the nic commands and the post-upgrade checks; run the node-pool upgrade above before you verify.

Run the destroy commands as described in Destroy a cluster.

nic destroy removes the AKS cluster and its node pools, the virtual network (when nic created it), the managed identities, the load balancer, any DNS records nic created, and (when nic created it) the <project_name>-rg resource group. When it finishes, it lists any resources tagged for the cluster that are still present, with an az resource delete command to remove them.

It does not remove the shared nic-tfstate-rg state backend; remove that by hand once you are finished with every cluster in the subscription.