In this chapter, we will develop a simple Pulumi program that creates a single virtual machine with a basic web server running on it. Our goal is to become acquainted with the Pulumi CLI, understand the structure of a Pulumi program, and learn how to create multiple stacks and override default values.
Pulumi is an open-source infrastructure-as-code tool for creating, deploying and managing cloud infrastructure. Pulumi works with traditional infrastructures like VMs, networks, and databases and modern architectures, including containers, Kubernetes clusters, and serverless functions. Pulumi supports dozens of public, private, and hybrid cloud service providers.
Pulumi is a multi-language infrastructure as Code tool using imperative languages to create a declarative infrastructure description.
You have a wide range of programming languages available, and you can use the one you and your team are the most comfortable with. Currently, (6/2023) Pulumi supports the following languages:
Node.js (JavaScript / TypeScript)
Python
Go
Java
.NET (C#, VB, F#)
YAML
The CLI instructions assume you’re using the Azure CLI (az).
Log in to the Azure CLI and Pulumi will automatically use your credentials:
az login
A web browser has been opened at https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize. Please continue the login in the web browser. If no web browser is available or if the web browser fails to open, use device code flow with `az login --use-device-code`.
Do as instructed to log in. After completed, az login will return and you are ready to go.
az account list
Pick out the
az account set --subscription=<id>
If you run Pulumi for the first time, you will be asked to log in. Follow the instructions on the screen to login. You may need to create an account first, don’t worry it is free.
To initialize a new Pulumi project, run pulumi new
and select from all the available templates the azure-<language>
.
The language is the programming language you want to use. The example below uses Go.
pulumi new azure-go --dir hello-azure-go
You will be guided through a wizard to create a new Pulumi project. You can use the following values:
project name (hello-azure-go):
project description (A minimal Azure Native Go Pulumi program):
Created project 'hello-azure-go'
Please enter your desired stack name.
To create a stack in an organization, use the format <org-name>/<stack-name> (e.g. `acmecorp/dev`).
stack name (dev):
Created stack 'dev'
azure-native:location: The Azure location to use (WestUS2): WestEurope
The template azure-go
will create a new Pulumi project with
the Pulumi Azure Native provider already installed. For
detailed instructions, refer to the Pulumi Azure Native Provider documentation.
Remove all code from the main.go
file and replace it with the following code, we will add more resources later on.
Now, let’s begin adding resources to our Pulumi program, starting with a basic virtual machine.
For a comprehensive list of available options, consult the Pulumi Azure Native provider documentation or utilize your Intellisense for code completion.
To gather more information about the available images and instance types, execute the following ‘az’ commands.
az vm list-sizes --location "westeurope" --output table
az vm image list -p canonical -f 0001-com-ubuntu-server-lunar --all -o table --location "westeurope" -s 23_04-gen2
Please use and 0001-com-ubuntu-server-lunar/Canonical/23_04-gen2
image and a Standard_B2s
instance type.
We start with the resource group. The resource group is a logical container for resources deployed on Azure.
package main
import (
"github.com/pulumi/pulumi-azure-native-sdk/compute/v2"
"github.com/pulumi/pulumi-azure-native-sdk/network/v2"
"github.com/pulumi/pulumi-azure-native-sdk/resources/v2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
resourceGroup, err := resources.NewResourceGroup(ctx, "resourceGroup", nil)
if err != nil {
return err
}
return nil
})
}
Next, we add all the networking resources to the program. We need a virtual network, a subnet, and a network interface.
package main
import (
"github.com/pulumi/pulumi-azure-native-sdk/compute/v2"
"github.com/pulumi/pulumi-azure-native-sdk/network/v2"
"github.com/pulumi/pulumi-azure-native-sdk/resources/v2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
resourceGroup, err := resources.NewResourceGroup(ctx, "resourceGroup", nil)
if err != nil {
return err
}
virtualNetwork, err := network.NewVirtualNetwork(ctx, "virtualNetwork", &network.VirtualNetworkArgs{
ResourceGroupName: resourceGroup.Name,
AddressSpace: &network.AddressSpaceArgs{
AddressPrefixes: pulumi.StringArray{
pulumi.String("10.0.0.0/16"),
},
},
})
if err != nil {
return err
}
subnet, err := network.NewSubnet(ctx, "subnet", &network.SubnetArgs{
ResourceGroupName: resourceGroup.Name,
VirtualNetworkName: virtualNetwork.Name,
AddressPrefix: pulumi.String("10.0.1.0/24"),
})
if err != nil {
return err
}
networkInterface, err := network.NewNetworkInterface(ctx, "networkInterface", &network.NetworkInterfaceArgs{
ResourceGroupName: resourceGroup.Name,
IpConfigurations: network.NetworkInterfaceIPConfigurationArray{
&network.NetworkInterfaceIPConfigurationArgs{
Name: pulumi.String("test-ip-config"),
Primary: pulumi.Bool(true),
Subnet: &network.SubnetTypeArgs{
Id: subnet.ID(),
},
},
},
})
if err != nil {
return err
}
return nil
})
}
Finally, we add the virtual machine to the program.
package main
import (
"github.com/pulumi/pulumi-azure-native-sdk/compute/v2"
"github.com/pulumi/pulumi-azure-native-sdk/network/v2"
"github.com/pulumi/pulumi-azure-native-sdk/resources/v2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
resourceGroup, err := resources.NewResourceGroup(ctx, "resourceGroup", nil)
if err != nil {
return err
}
virtualNetwork, err := network.NewVirtualNetwork(ctx, "virtualNetwork", &network.VirtualNetworkArgs{
ResourceGroupName: resourceGroup.Name,
AddressSpace: &network.AddressSpaceArgs{
AddressPrefixes: pulumi.StringArray{
pulumi.String("10.0.0.0/16"),
},
},
})
if err != nil {
return err
}
subnet, err := network.NewSubnet(ctx, "subnet", &network.SubnetArgs{
ResourceGroupName: resourceGroup.Name,
VirtualNetworkName: virtualNetwork.Name,
AddressPrefix: pulumi.String("10.0.1.0/24"),
})
if err != nil {
return err
}
networkInterface, err := network.NewNetworkInterface(ctx, "networkInterface", &network.NetworkInterfaceArgs{
ResourceGroupName: resourceGroup.Name,
IpConfigurations: network.NetworkInterfaceIPConfigurationArray{
&network.NetworkInterfaceIPConfigurationArgs{
Name: pulumi.String("test-ip-config"),
Primary: pulumi.Bool(true),
Subnet: &network.SubnetTypeArgs{
Id: subnet.ID(),
},
},
},
})
if err != nil {
return err
}
virtualMachine, err := compute.NewVirtualMachine(ctx, "virtualMachine", &compute.VirtualMachineArgs{
NetworkProfile: &compute.NetworkProfileArgs{
NetworkInterfaces: compute.NetworkInterfaceReferenceArray{
&compute.NetworkInterfaceReferenceArgs{
Id: networkInterface.ID(),
},
},
},
HardwareProfile: &compute.HardwareProfileArgs{
VmSize: pulumi.String("Standard_B2s"),
},
OsProfile: &compute.OSProfileArgs{
ComputerName: pulumi.String("HelloVM"),
AdminUsername: pulumi.String("azureuser"),
AdminPassword: pulumi.String("Password1234!"),
},
ResourceGroupName: resourceGroup.Name,
StorageProfile: &compute.StorageProfileArgs{
ImageReference: &compute.ImageReferenceArgs{
Offer: pulumi.String("0001-com-ubuntu-server-lunar"),
Publisher: pulumi.String("Canonical"),
Sku: pulumi.String("23_04-gen2"),
Version: pulumi.String("latest"),
},
},
VmName: pulumi.String("HelloVM"),
})
if err != nil {
return err
}
return nil
})
}
At the end we output the name of the virtual machine.
package main
import (
"github.com/pulumi/pulumi-azure-native-sdk/compute/v2"
"github.com/pulumi/pulumi-azure-native-sdk/network/v2"
"github.com/pulumi/pulumi-azure-native-sdk/resources/v2"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
resourceGroup, err := resources.NewResourceGroup(ctx, "resourceGroup", nil)
if err != nil {
return err
}
virtualNetwork, err := network.NewVirtualNetwork(ctx, "virtualNetwork", &network.VirtualNetworkArgs{
ResourceGroupName: resourceGroup.Name,
AddressSpace: &network.AddressSpaceArgs{
AddressPrefixes: pulumi.StringArray{
pulumi.String("10.0.0.0/16"),
},
},
})
if err != nil {
return err
}
subnet, err := network.NewSubnet(ctx, "subnet", &network.SubnetArgs{
ResourceGroupName: resourceGroup.Name,
VirtualNetworkName: virtualNetwork.Name,
AddressPrefix: pulumi.String("10.0.1.0/24"),
})
if err != nil {
return err
}
networkInterface, err := network.NewNetworkInterface(ctx, "networkInterface", &network.NetworkInterfaceArgs{
ResourceGroupName: resourceGroup.Name,
IpConfigurations: network.NetworkInterfaceIPConfigurationArray{
&network.NetworkInterfaceIPConfigurationArgs{
Name: pulumi.String("test-ip-config"),
Primary: pulumi.Bool(true),
Subnet: &network.SubnetTypeArgs{
Id: subnet.ID(),
},
},
},
})
if err != nil {
return err
}
virtualMachine, err := compute.NewVirtualMachine(ctx, "virtualMachine", &compute.VirtualMachineArgs{
NetworkProfile: &compute.NetworkProfileArgs{
NetworkInterfaces: compute.NetworkInterfaceReferenceArray{
&compute.NetworkInterfaceReferenceArgs{
Id: networkInterface.ID(),
},
},
},
HardwareProfile: &compute.HardwareProfileArgs{
VmSize: pulumi.String("Standard_B2s"),
},
OsProfile: &compute.OSProfileArgs{
ComputerName: pulumi.String("HelloVM"),
AdminUsername: pulumi.String("azureuser"),
AdminPassword: pulumi.String("Password1234!"),
},
ResourceGroupName: resourceGroup.Name,
StorageProfile: &compute.StorageProfileArgs{
ImageReference: &compute.ImageReferenceArgs{
Offer: pulumi.String("0001-com-ubuntu-server-lunar"),
Publisher: pulumi.String("Canonical"),
Sku: pulumi.String("23_04-gen2"),
Version: pulumi.String("latest"),
},
},
VmName: pulumi.String("HelloVM"),
})
if err != nil {
return err
}
ctx.Export("vmName", virtualMachine.Name)
return nil
})
}
Before you can run
pulumi up
, you need to be sure that your Azure credentials are set.
pulumi up
This command will show you a preview of all the resources and asks you if you want to deploy them. You can run dedicated commands to see the preview or to deploy the resources.
pulumi preview
# or
pulumi up
To destroy the stack, run the following command.
pulumi destroy
pulumi stack rm <stack-name>
And confirm the destruction with yes
.
To switch between stacks, you can use the following command.
pulumi stack select <stack-name>