All articles

Create an Azure VM using C# and Azure.ResourceManager

Provision a complete Azure Linux VM from C# with the modern Azure.ResourceManager SDK — DefaultAzureCredential auth, VNet, public IP, NIC, SSH-key login, and a migration map from the deprecated Fluent

0 · log in to like, save & follow Share on LinkedIn Share on X

Creating Azure virtual machines from C# used to go through the Fluent management SDK (Microsoft.Azure.Management.Fluent). That SDK is deprecated — its replacement is the Azure.ResourceManager.* family (the "track 2" management SDK), with unified authentication through Azure.Identity. This tutorial provisions a complete Linux VM — resource group, virtual network, public IP, NIC, and the machine itself — with the current SDK, end to end.

cover

If you only need one VM once, the portal or a Bicep template is less code. Reach for the SDK when VM creation is part of your application's own logic — provisioning environments per customer, building internal tooling, or automating fleets.

Install the packages

dotnet new console -n CreateAzureVm
cd CreateAzureVm
dotnet add package Azure.Identity
dotnet add package Azure.ResourceManager
dotnet add package Azure.ResourceManager.Compute
dotnet add package Azure.ResourceManager.Network

Authenticate with DefaultAzureCredential

The old SDK's SdkContext.AzureCredentialsFactory with a credentials file is gone. DefaultAzureCredential tries a chain of sources — environment variables, managed identity, Azure CLI login, Visual Studio — so the same code works on your laptop (via az login) and in production (via managed identity) with no code change:

using Azure.Identity;
using Azure.ResourceManager;

var client = new ArmClient(new DefaultAzureCredential());
var subscription = await client.GetDefaultSubscriptionAsync();

For unattended automation with a service principal, set AZURE_TENANT_ID, AZURE_CLIENT_ID, and AZURE_CLIENT_SECRET — how to obtain them is covered in get Azure subscription, tenant, client id and client secret.

Create the resource group

Everything for this VM lives in one resource group, so cleanup later is a single delete:

var location = AzureLocation.EastUS;

var resourceGroup = (await subscription.GetResourceGroups()
    .CreateOrUpdateAsync(WaitUntil.Completed, "geekstore-vm-rg",
        new ResourceGroupData(location))).Value;

The pattern you see here repeats for every resource type: get the collection (GetResourceGroups()), call CreateOrUpdateAsync, and WaitUntil.Completed makes the call block until provisioning finishes (pass WaitUntil.Started to fire-and-poll instead).

Network: VNet, public IP, and NIC

A VM needs a network interface, which needs a subnet and (to be reachable) a public IP.

// virtual network with one subnet
var vnetData = new VirtualNetworkData
{
    Location = location,
    AddressPrefixes = { "10.0.0.0/16" },
    Subnets = { new SubnetData { Name = "default", AddressPrefix = "10.0.0.0/24" } },
};
var vnet = (await resourceGroup.GetVirtualNetworks()
    .CreateOrUpdateAsync(WaitUntil.Completed, "geekstore-vnet", vnetData)).Value;

// static public IP
var ipData = new PublicIPAddressData
{
    Location = location,
    PublicIPAllocationMethod = NetworkIPAllocationMethod.Static,
    Sku = new PublicIPAddressSku { Name = PublicIPAddressSkuName.Standard },
};
var publicIp = (await resourceGroup.GetPublicIPAddresses()
    .CreateOrUpdateAsync(WaitUntil.Completed, "geekstore-ip", ipData)).Value;

// NIC joining the subnet and the public IP
var nicData = new NetworkInterfaceData
{
    Location = location,
    IPConfigurations =
    {
        new NetworkInterfaceIPConfigurationData
        {
            Name = "primary",
            Primary = true,
            Subnet = new SubnetData { Id = vnet.Data.Subnets[0].Id },
            PrivateIPAllocationMethod = NetworkIPAllocationMethod.Dynamic,
            PublicIPAddress = new PublicIPAddressData { Id = publicIp.Id },
        }
    },
};
var nic = (await resourceGroup.GetNetworkInterfaces()
    .CreateOrUpdateAsync(WaitUntil.Completed, "geekstore-nic", nicData)).Value;

Create the virtual machine

SSH-key authentication with password login disabled — the same setup as creating an Azure Linux VM with an SSH key pair, done in code:

var vmData = new VirtualMachineData(location)
{
    HardwareProfile = new VirtualMachineHardwareProfile
    {
        VmSize = VirtualMachineSizeType.StandardB2S,   // 2 vCPU burstable — cheap dev box
    },
    OSProfile = new VirtualMachineOSProfile
    {
        ComputerName = "geekstore-vm",
        AdminUsername = "azureuser",
        LinuxConfiguration = new LinuxConfiguration
        {
            DisablePasswordAuthentication = true,
            SshPublicKeys =
            {
                new SshPublicKeyConfiguration
                {
                    Path = "/home/azureuser/.ssh/authorized_keys",
                    KeyData = File.ReadAllText(Path.Combine(
                        Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
                        ".ssh", "id_rsa.pub")),
                }
            },
        },
    },
    StorageProfile = new VirtualMachineStorageProfile
    {
        ImageReference = new ImageReference
        {
            Publisher = "Canonical",
            Offer = "ubuntu-24_04-lts",
            Sku = "server",
            Version = "latest",
        },
        OSDisk = new VirtualMachineOSDisk(DiskCreateOptionType.FromImage)
        {
            ManagedDisk = new VirtualMachineManagedDisk
            {
                StorageAccountType = StorageAccountType.StandardSsdLrs,
            },
        },
    },
    NetworkProfile = new VirtualMachineNetworkProfile
    {
        NetworkInterfaces =
        {
            new VirtualMachineNetworkInterfaceReference { Id = nic.Id, Primary = true }
        },
    },
};

var vm = (await resourceGroup.GetVirtualMachines()
    .CreateOrUpdateAsync(WaitUntil.Completed, "geekstore-vm", vmData)).Value;

Console.WriteLine($"VM created: {vm.Data.Name}");
Console.WriteLine($"Public IP:  {(await publicIp.GetAsync()).Value.Data.IPAddress}");

Run it, wait a couple of minutes, and connect:

ssh azureuser@<public ip>

Migrating from the Fluent SDK — a quick map

Fluent SDK (deprecated) Azure.ResourceManager
Microsoft.Azure.Management.Fluent Azure.ResourceManager.* per service
SdkContext.AzureCredentialsFactory DefaultAzureCredential (Azure.Identity)
azure.VirtualMachines.Define(...).WithRegion(...)...Create() Build a VirtualMachineData, call CreateOrUpdateAsync
Fluent method chaining Plain data objects — easier to build conditionally and unit test

The shape changed from a fluent chain to data objects plus explicit CreateOrUpdateAsync calls — slightly more verbose, but every step is awaitable, retryable, and testable on its own.

Clean up

One call removes everything the program created:

await resourceGroup.DeleteAsync(WaitUntil.Completed);

A B2s VM, disk, and static IP cost real money while they exist — delete the resource group when you're done experimenting.

Enjoyed this article? Get the best GeeksArray articles in your inbox — once a week, no spam, unsubscribe anytime.

Comments (0)

Log in to join the conversation.

No comments yet — be the first to share your thoughts.