Quickstart: Create a private link service using Bicep
In this quickstart, you use Bicep to create a private link service.
Bicep is a domain-specific language (DSL) that uses declarative syntax to deploy Azure resources. It provides concise syntax, reliable type safety, and support for code reuse. Bicep offers the best authoring experience for your infrastructure-as-code solutions in Azure.
Prerequisites
You need an Azure account with an active subscription. Create an account for free.
Review the Bicep file
This Bicep file creates a private link service.
The Bicep file used in this quickstart is from Azure Quickstart Templates.
@description('Username for the Virtual Machine.')
param vmAdminUsername string
@description('Password for the Virtual Machine. The password must be at least 12 characters long and have lower case, upper characters, digit and a special character (Regex match)')
@secure()
param vmAdminPassword string
@description('The size of the VM')
param vmSize string = 'Standard_D2_v3'
@description('Location for all resources.')
param location string = resourceGroup().location
var vnetName = 'myVirtualNetwork'
var vnetConsumerName = 'myPEVnet'
var vnetAddressPrefix = '10.0.0.0/16'
var frontendSubnetPrefix = '10.0.1.0/24'
var frontendSubnetName = 'frontendSubnet'
var backendSubnetPrefix = '10.0.2.0/24'
var backendSubnetName = 'backendSubnet'
var consumerSubnetPrefix = '10.0.0.0/24'
var consumerSubnetName = 'myPESubnet'
var loadbalancerName = 'myILB'
var backendPoolName = 'myBackEndPool'
var loadBalancerFrontEndIpConfigurationName = 'myFrontEnd'
var healthProbeName = 'myHealthProbe'
var privateEndpointName = 'myPrivateEndpoint'
var vmName = take('myVm${uniqueString(resourceGroup().id)}', 15)
var networkInterfaceName = '${vmName}NetInt'
var vmConsumerName = take('myConsumerVm${uniqueString(resourceGroup().id)}', 15)
var publicIpAddressConsumerName = '${vmConsumerName}PublicIP'
var networkInterfaceConsumerName = '${vmConsumerName}NetInt'
var osDiskType = 'StandardSSD_LRS'
var privatelinkServiceName = 'myPLS'
var loadbalancerId = loadbalancer.id
resource vnet 'Microsoft.Network/virtualNetworks@2021-05-01' = {
name: vnetName
location: location
properties: {
addressSpace: {
addressPrefixes: [
vnetAddressPrefix
]
}
subnets: [
{
name: frontendSubnetName
properties: {
addressPrefix: frontendSubnetPrefix
privateLinkServiceNetworkPolicies: 'Disabled'
}
}
{
name: backendSubnetName
properties: {
addressPrefix: backendSubnetPrefix
}
}
]
}
}
resource loadbalancer 'Microsoft.Network/loadBalancers@2021-05-01' = {
name: loadbalancerName
location: location
sku: {
name: 'Standard'
}
properties: {
frontendIPConfigurations: [
{
name: loadBalancerFrontEndIpConfigurationName
properties: {
privateIPAllocationMethod: 'Dynamic'
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, frontendSubnetName)
}
}
}
]
backendAddressPools: [
{
name: backendPoolName
}
]
inboundNatRules: [
{
name: 'RDP-VM0'
properties: {
frontendIPConfiguration: {
id: resourceId('Microsoft.Network/loadBalancers/frontendIpConfigurations', loadbalancerName, loadBalancerFrontEndIpConfigurationName)
}
protocol: 'Tcp'
frontendPort: 3389
backendPort: 3389
enableFloatingIP: false
}
}
]
loadBalancingRules: [
{
name: 'myHTTPRule'
properties: {
frontendIPConfiguration: {
id: resourceId('Microsoft.Network/loadBalancers/frontendIpConfigurations', loadbalancerName, loadBalancerFrontEndIpConfigurationName)
}
backendAddressPool: {
id: resourceId('Microsoft.Network/loadBalancers/backendAddressPools', loadbalancerName, backendPoolName)
}
probe: {
id: resourceId('Microsoft.Network/loadBalancers/probes', loadbalancerName, healthProbeName)
}
protocol: 'Tcp'
frontendPort: 80
backendPort: 80
idleTimeoutInMinutes: 15
}
}
]
probes: [
{
properties: {
protocol: 'Tcp'
port: 80
intervalInSeconds: 15
numberOfProbes: 2
}
name: healthProbeName
}
]
}
dependsOn: [
vnet
]
}
resource networkInterface 'Microsoft.Network/networkInterfaces@2021-05-01' = {
name: networkInterfaceName
location: location
tags: {
displayName: networkInterfaceName
}
properties: {
ipConfigurations: [
{
name: 'ipConfig1'
properties: {
privateIPAllocationMethod: 'Dynamic'
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetName, backendSubnetName)
}
loadBalancerBackendAddressPools: [
{
id: resourceId('Microsoft.Network/loadBalancers/backendAddressPools', loadbalancerName, backendPoolName)
}
]
loadBalancerInboundNatRules: [
{
id: resourceId('Microsoft.Network/loadBalancers/inboundNatRules/', loadbalancerName, 'RDP-VM0')
}
]
}
}
]
}
dependsOn: [
loadbalancer
]
}
resource vm 'Microsoft.Compute/virtualMachines@2021-11-01' = {
name: vmName
location: location
tags: {
displayName: vmName
}
properties: {
hardwareProfile: {
vmSize: vmSize
}
osProfile: {
computerName: vmName
adminUsername: vmAdminUsername
adminPassword: vmAdminPassword
}
storageProfile: {
imageReference: {
publisher: 'MicrosoftWindowsServer'
offer: 'WindowsServer'
sku: '2019-Datacenter'
version: 'latest'
}
osDisk: {
name: '${vmName}OsDisk'
caching: 'ReadWrite'
createOption: 'FromImage'
managedDisk: {
storageAccountType: osDiskType
}
diskSizeGB: 128
}
}
networkProfile: {
networkInterfaces: [
{
id: networkInterface.id
}
]
}
}
}
resource vmExtension 'Microsoft.Compute/virtualMachines/extensions@2021-11-01' = {
parent: vm
name: 'installcustomscript'
location: location
tags: {
displayName: 'install software for Windows VM'
}
properties: {
publisher: 'Microsoft.Compute'
type: 'CustomScriptExtension'
typeHandlerVersion: '1.9'
autoUpgradeMinorVersion: true
protectedSettings: {
commandToExecute: 'powershell -ExecutionPolicy Unrestricted Install-WindowsFeature -Name Web-Server'
}
}
}
resource privatelinkService 'Microsoft.Network/privateLinkServices@2021-05-01' = {
name: privatelinkServiceName
location: location
properties: {
enableProxyProtocol: false
loadBalancerFrontendIpConfigurations: [
{
id: resourceId('Microsoft.Network/loadBalancers/frontendIpConfigurations', loadbalancerName, loadBalancerFrontEndIpConfigurationName)
}
]
ipConfigurations: [
{
name: 'snet-provider-default-1'
properties: {
privateIPAllocationMethod: 'Dynamic'
privateIPAddressVersion: 'IPv4'
subnet: {
id: reference(loadbalancerId, '2019-06-01').frontendIPConfigurations[0].properties.subnet.id
}
primary: false
}
}
]
}
}
resource vnetConsumer 'Microsoft.Network/virtualNetworks@2021-05-01' = {
name: vnetConsumerName
location: location
properties: {
addressSpace: {
addressPrefixes: [
vnetAddressPrefix
]
}
subnets: [
{
name: consumerSubnetName
properties: {
addressPrefix: consumerSubnetPrefix
privateEndpointNetworkPolicies: 'Disabled'
}
}
{
name: backendSubnetName
properties: {
addressPrefix: backendSubnetPrefix
}
}
]
}
}
resource publicIpAddressConsumer 'Microsoft.Network/publicIPAddresses@2021-05-01' = {
name: publicIpAddressConsumerName
location: location
tags: {
displayName: publicIpAddressConsumerName
}
properties: {
publicIPAllocationMethod: 'Dynamic'
dnsSettings: {
domainNameLabel: toLower(vmConsumerName)
}
}
}
resource networkInterfaceConsumer 'Microsoft.Network/networkInterfaces@2021-05-01' = {
name: networkInterfaceConsumerName
location: location
tags: {
displayName: networkInterfaceConsumerName
}
properties: {
ipConfigurations: [
{
name: 'ipConfig1'
properties: {
privateIPAllocationMethod: 'Dynamic'
publicIPAddress: {
id: publicIpAddressConsumer.id
}
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetConsumerName, consumerSubnetName)
}
}
}
]
}
dependsOn: [
vnetConsumer
]
}
resource vmConsumer 'Microsoft.Compute/virtualMachines@2021-11-01' = {
name: vmConsumerName
location: location
tags: {
displayName: vmConsumerName
}
properties: {
hardwareProfile: {
vmSize: vmSize
}
osProfile: {
computerName: vmConsumerName
adminUsername: vmAdminUsername
adminPassword: vmAdminPassword
}
storageProfile: {
imageReference: {
publisher: 'MicrosoftWindowsServer'
offer: 'WindowsServer'
sku: '2019-Datacenter'
version: 'latest'
}
osDisk: {
name: '${vmConsumerName}OsDisk'
caching: 'ReadWrite'
createOption: 'FromImage'
managedDisk: {
storageAccountType: osDiskType
}
diskSizeGB: 128
}
}
networkProfile: {
networkInterfaces: [
{
id: networkInterfaceConsumer.id
}
]
}
}
}
resource privateEndpoint 'Microsoft.Network/privateEndpoints@2021-05-01' = {
name: privateEndpointName
location: location
properties: {
subnet: {
id: resourceId('Microsoft.Network/virtualNetworks/subnets', vnetConsumerName, consumerSubnetName)
}
privateLinkServiceConnections: [
{
name: privateEndpointName
properties: {
privateLinkServiceId: privatelinkService.id
}
}
]
}
dependsOn: [
vnetConsumer
]
}
Multiple Azure resources are defined in the Bicep file:
- Microsoft.Network/virtualNetworks: There's one virtual network for each virtual machine.
- Microsoft.Network/loadBalancers: The load balancer that exposes the virtual machines that host the service.
- Microsoft.Network/networkInterfaces: There are two network interfaces, one for each virtual machine.
- Microsoft.Compute/virtualMachines: There are two virtual machines, one that hosts the service and one that tests the connection to the private endpoint.
- Microsoft.Compute/virtualMachines/extensions: The extension that installs a web server.
- Microsoft.Network/privateLinkServices: The private link service to expose the service.
- Microsoft.Network/publicIpAddresses: There is a public IP address for the test virtual machine.
- Microsoft.Network/privateendpoints: The private endpoint to access the service.
Deploy the Bicep file
Save the Bicep file as main.bicep to your local computer.
Deploy the Bicep file using either Azure CLI or Azure PowerShell.
az group create --name exampleRG --location eastus az deployment group create --resource-group exampleRG --template-file main.bicep --parameters vmAdminUsername=<admin-user>
Note
Replace <admin-user> with the username for the virtual machine. You'll also be prompted to enter vmAdminPassword. The password must be at least 12 characters long and have uppercase and lowercase characters, a digit, and a special character.
When the deployment finishes, you should see a message indicating the deployment succeeded.
Review deployed resources
Use the Azure portal, Azure CLI, or Azure PowerShell to list the deployed resources in the resource group.
az resource list --resource-group exampleRG
Validate the deployment
Note
The Bicep file generates a unique name for the virtual machine myConsumerVm{uniqueid} resource. Substitute your generated value for {uniqueid}.
Connect to a VM from the internet
Connect to the VM myConsumerVm{uniqueid} from the internet as follows:
In the Azure portal search bar, enter myConsumerVm{uniqueid}.
Select Connect. Connect to virtual machine opens.
Select Download RDP File. Azure creates a Remote Desktop Protocol (.rdp) file and downloads it to your computer.
Open the downloaded .rdp file.
a. If prompted, select Connect.
b. Enter the username and password you specified when you created the VM.
Note
You might need to select More choices > Use a different account, to specify the credentials you entered when you created the VM.
Select OK.
You might receive a certificate warning during the sign-in process. If you receive a certificate warning, select Yes or Continue.
After the VM desktop appears, minimize it to go back to your local desktop.
Access the http service privately from the VM
Here's how to connect to the http service from the VM by using the private endpoint.
- Go to the Remote Desktop of myConsumerVm{uniqueid}.
- Open a browser, and enter the private endpoint address:
http://10.0.0.5/
. - The default IIS page appears.
Clean up resources
When you no longer need the resources that you created with the private link service, delete the resource group. This removes the private link service and all the related resources.
az group delete --name exampleRG
Next steps
For more information on the services that support a private endpoint, see: