The azure-native:network:BastionHost resource, part of the Pulumi Azure Native provider, provisions an Azure Bastion Host that provides secure browser-based RDP and SSH access to virtual machines without exposing them to the public internet. This guide focuses on three capabilities: standard deployment with public access, private-only mode for network-isolated environments, and Developer SKU with IP-based access control.
Bastion Hosts require a virtual network with a subnet named exactly “BastionHostSubnet” and, for standard deployments, a public IP address. The examples are intentionally small. Combine them with your own virtual networks, subnets, and access policies.
Deploy a standard Bastion Host with public access
Most deployments use Bastion to enable browser-based VM access without assigning public IPs to target VMs. The standard configuration requires a public IP and a dedicated subnet.
import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";
const bastionHost = new azure_native.network.BastionHost("bastionHost", {
bastionHostName: "bastionhosttenant",
ipConfigurations: [{
name: "bastionHostIpConfiguration",
publicIPAddress: {
id: "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/publicIPAddresses/pipName",
},
subnet: {
id: "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet",
},
}],
resourceGroupName: "rg1",
});
import pulumi
import pulumi_azure_native as azure_native
bastion_host = azure_native.network.BastionHost("bastionHost",
bastion_host_name="bastionhosttenant",
ip_configurations=[{
"name": "bastionHostIpConfiguration",
"public_ip_address": {
"id": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/publicIPAddresses/pipName",
},
"subnet": {
"id": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet",
},
}],
resource_group_name="rg1")
package main
import (
network "github.com/pulumi/pulumi-azure-native-sdk/network/v3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := network.NewBastionHost(ctx, "bastionHost", &network.BastionHostArgs{
BastionHostName: pulumi.String("bastionhosttenant"),
IpConfigurations: network.BastionHostIPConfigurationArray{
&network.BastionHostIPConfigurationArgs{
Name: pulumi.String("bastionHostIpConfiguration"),
PublicIPAddress: &network.SubResourceArgs{
Id: pulumi.String("/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/publicIPAddresses/pipName"),
},
Subnet: &network.SubResourceArgs{
Id: pulumi.String("/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet"),
},
},
},
ResourceGroupName: pulumi.String("rg1"),
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using AzureNative = Pulumi.AzureNative;
return await Deployment.RunAsync(() =>
{
var bastionHost = new AzureNative.Network.BastionHost("bastionHost", new()
{
BastionHostName = "bastionhosttenant",
IpConfigurations = new[]
{
new AzureNative.Network.Inputs.BastionHostIPConfigurationArgs
{
Name = "bastionHostIpConfiguration",
PublicIPAddress = new AzureNative.Network.Inputs.SubResourceArgs
{
Id = "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/publicIPAddresses/pipName",
},
Subnet = new AzureNative.Network.Inputs.SubResourceArgs
{
Id = "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet",
},
},
},
ResourceGroupName = "rg1",
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.azurenative.network.BastionHost;
import com.pulumi.azurenative.network.BastionHostArgs;
import com.pulumi.azurenative.network.inputs.BastionHostIPConfigurationArgs;
import com.pulumi.azurenative.network.inputs.SubResourceArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var bastionHost = new BastionHost("bastionHost", BastionHostArgs.builder()
.bastionHostName("bastionhosttenant")
.ipConfigurations(BastionHostIPConfigurationArgs.builder()
.name("bastionHostIpConfiguration")
.publicIPAddress(SubResourceArgs.builder()
.id("/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/publicIPAddresses/pipName")
.build())
.subnet(SubResourceArgs.builder()
.id("/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet")
.build())
.build())
.resourceGroupName("rg1")
.build());
}
}
resources:
bastionHost:
type: azure-native:network:BastionHost
properties:
bastionHostName: bastionhosttenant
ipConfigurations:
- name: bastionHostIpConfiguration
publicIPAddress:
id: /subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/publicIPAddresses/pipName
subnet:
id: /subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet
resourceGroupName: rg1
The ipConfigurations array defines how Bastion connects to your network. The publicIPAddress property provides the public endpoint users connect to, while the subnet property must reference a subnet named exactly “BastionHostSubnet”. Azure routes traffic from the Bastion Host to your VMs through this subnet.
Deploy a private-only Bastion Host
Organizations with strict network isolation can deploy Bastion without a public IP, restricting access to users already on the virtual network via VPN or ExpressRoute.
import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";
const bastionHost = new azure_native.network.BastionHost("bastionHost", {
bastionHostName: "bastionhosttenant",
enablePrivateOnlyBastion: true,
ipConfigurations: [{
name: "bastionHostIpConfiguration",
subnet: {
id: "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet",
},
}],
resourceGroupName: "rg1",
});
import pulumi
import pulumi_azure_native as azure_native
bastion_host = azure_native.network.BastionHost("bastionHost",
bastion_host_name="bastionhosttenant",
enable_private_only_bastion=True,
ip_configurations=[{
"name": "bastionHostIpConfiguration",
"subnet": {
"id": "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet",
},
}],
resource_group_name="rg1")
package main
import (
network "github.com/pulumi/pulumi-azure-native-sdk/network/v3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := network.NewBastionHost(ctx, "bastionHost", &network.BastionHostArgs{
BastionHostName: pulumi.String("bastionhosttenant"),
EnablePrivateOnlyBastion: pulumi.Bool(true),
IpConfigurations: network.BastionHostIPConfigurationArray{
&network.BastionHostIPConfigurationArgs{
Name: pulumi.String("bastionHostIpConfiguration"),
Subnet: &network.SubResourceArgs{
Id: pulumi.String("/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet"),
},
},
},
ResourceGroupName: pulumi.String("rg1"),
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using AzureNative = Pulumi.AzureNative;
return await Deployment.RunAsync(() =>
{
var bastionHost = new AzureNative.Network.BastionHost("bastionHost", new()
{
BastionHostName = "bastionhosttenant",
EnablePrivateOnlyBastion = true,
IpConfigurations = new[]
{
new AzureNative.Network.Inputs.BastionHostIPConfigurationArgs
{
Name = "bastionHostIpConfiguration",
Subnet = new AzureNative.Network.Inputs.SubResourceArgs
{
Id = "/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet",
},
},
},
ResourceGroupName = "rg1",
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.azurenative.network.BastionHost;
import com.pulumi.azurenative.network.BastionHostArgs;
import com.pulumi.azurenative.network.inputs.BastionHostIPConfigurationArgs;
import com.pulumi.azurenative.network.inputs.SubResourceArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var bastionHost = new BastionHost("bastionHost", BastionHostArgs.builder()
.bastionHostName("bastionhosttenant")
.enablePrivateOnlyBastion(true)
.ipConfigurations(BastionHostIPConfigurationArgs.builder()
.name("bastionHostIpConfiguration")
.subnet(SubResourceArgs.builder()
.id("/subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet")
.build())
.build())
.resourceGroupName("rg1")
.build());
}
}
resources:
bastionHost:
type: azure-native:network:BastionHost
properties:
bastionHostName: bastionhosttenant
enablePrivateOnlyBastion: true
ipConfigurations:
- name: bastionHostIpConfiguration
subnet:
id: /subscriptions/subid/resourceGroups/rg1/providers/Microsoft.Network/virtualNetworks/vnet2/subnets/BastionHostSubnet
resourceGroupName: rg1
Setting enablePrivateOnlyBastion to true removes the public IP requirement. Users must have existing connectivity to the virtual network through VPN, ExpressRoute, or peering. The ipConfigurations array still requires the BastionHostSubnet reference, but omits the publicIPAddress property.
Deploy a Developer SKU Bastion with network ACLs
The Developer SKU provides a cost-effective option for development environments, with IP-based access control to limit connections.
import * as pulumi from "@pulumi/pulumi";
import * as azure_native from "@pulumi/azure-native";
const bastionHost = new azure_native.network.BastionHost("bastionHost", {
bastionHostName: "bastionhostdeveloper",
ipConfigurations: [],
networkAcls: {
ipRules: [{
addressPrefix: "1.1.1.1/16",
}],
},
resourceGroupName: "rg2",
virtualNetwork: {
id: "/subscriptions/subid/resourceGroups/rg2/providers/Microsoft.Network/virtualNetworks/vnet2",
},
});
import pulumi
import pulumi_azure_native as azure_native
bastion_host = azure_native.network.BastionHost("bastionHost",
bastion_host_name="bastionhostdeveloper",
ip_configurations=[],
network_acls={
"ip_rules": [{
"address_prefix": "1.1.1.1/16",
}],
},
resource_group_name="rg2",
virtual_network={
"id": "/subscriptions/subid/resourceGroups/rg2/providers/Microsoft.Network/virtualNetworks/vnet2",
})
package main
import (
network "github.com/pulumi/pulumi-azure-native-sdk/network/v3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := network.NewBastionHost(ctx, "bastionHost", &network.BastionHostArgs{
BastionHostName: pulumi.String("bastionhostdeveloper"),
IpConfigurations: network.BastionHostIPConfigurationArray{},
NetworkAcls: &network.BastionHostPropertiesFormatNetworkAclsArgs{
IpRules: network.IPRuleArray{
&network.IPRuleArgs{
AddressPrefix: pulumi.String("1.1.1.1/16"),
},
},
},
ResourceGroupName: pulumi.String("rg2"),
VirtualNetwork: &network.SubResourceArgs{
Id: pulumi.String("/subscriptions/subid/resourceGroups/rg2/providers/Microsoft.Network/virtualNetworks/vnet2"),
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using AzureNative = Pulumi.AzureNative;
return await Deployment.RunAsync(() =>
{
var bastionHost = new AzureNative.Network.BastionHost("bastionHost", new()
{
BastionHostName = "bastionhostdeveloper",
IpConfigurations = new[] {},
NetworkAcls = new AzureNative.Network.Inputs.BastionHostPropertiesFormatNetworkAclsArgs
{
IpRules = new[]
{
new AzureNative.Network.Inputs.IPRuleArgs
{
AddressPrefix = "1.1.1.1/16",
},
},
},
ResourceGroupName = "rg2",
VirtualNetwork = new AzureNative.Network.Inputs.SubResourceArgs
{
Id = "/subscriptions/subid/resourceGroups/rg2/providers/Microsoft.Network/virtualNetworks/vnet2",
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.azurenative.network.BastionHost;
import com.pulumi.azurenative.network.BastionHostArgs;
import com.pulumi.azurenative.network.inputs.BastionHostPropertiesFormatNetworkAclsArgs;
import com.pulumi.azurenative.network.inputs.SubResourceArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class App {
public static void main(String[] args) {
Pulumi.run(App::stack);
}
public static void stack(Context ctx) {
var bastionHost = new BastionHost("bastionHost", BastionHostArgs.builder()
.bastionHostName("bastionhostdeveloper")
.ipConfigurations()
.networkAcls(BastionHostPropertiesFormatNetworkAclsArgs.builder()
.ipRules(IPRuleArgs.builder()
.addressPrefix("1.1.1.1/16")
.build())
.build())
.resourceGroupName("rg2")
.virtualNetwork(SubResourceArgs.builder()
.id("/subscriptions/subid/resourceGroups/rg2/providers/Microsoft.Network/virtualNetworks/vnet2")
.build())
.build());
}
}
resources:
bastionHost:
type: azure-native:network:BastionHost
properties:
bastionHostName: bastionhostdeveloper
ipConfigurations: []
networkAcls:
ipRules:
- addressPrefix: 1.1.1.1/16
resourceGroupName: rg2
virtualNetwork:
id: /subscriptions/subid/resourceGroups/rg2/providers/Microsoft.Network/virtualNetworks/vnet2
The Developer SKU uses the virtualNetwork property instead of ipConfigurations, referencing the entire VNet rather than a specific subnet. The networkAcls property restricts access by source IP address; only clients matching the ipRules addressPrefix can connect. This configuration is useful for limiting Bastion access to corporate networks or specific developer IPs.
Beyond these examples
These snippets focus on specific Bastion Host features: standard and private-only deployment modes, and Developer SKU with network ACLs. They’re intentionally minimal rather than full network security solutions.
The examples reference pre-existing infrastructure such as virtual networks with BastionHostSubnet, and public IP addresses for standard mode. They focus on configuring the Bastion Host rather than provisioning the surrounding network.
To keep things focused, common Bastion patterns are omitted, including:
- Feature flags (enableFileCopy, enableTunneling, enableShareableLink, etc.)
- Scale units for capacity planning (scaleUnits)
- Availability zones for high availability (zones)
- SKU configuration (sku property)
These omissions are intentional: the goal is to illustrate how each Bastion feature is wired, not provide drop-in security modules. See the Bastion Host resource reference for all available configuration options.
Let's deploy Azure Bastion Hosts
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
Configuration & Deployment
ipConfigurations with a subnet and public IP address. Developer Bastion uses virtualNetwork with empty ipConfigurations and networkAcls for IP-based access control.enablePrivateOnlyBastion to true and configure ipConfigurations with only a subnet reference (omit publicIPAddress).enableFileCopy), IP Connect (enableIpConnect), Kerberos (enableKerberos), Session Recording (enableSessionRecording), Shareable Link (enableShareableLink), and Tunneling (enableTunneling). All default to false.Networking & Connectivity
BastionHostSubnet, as shown in all standard deployment examples.publicIPAddress in ipConfigurations. Private-only Bastion (with enablePrivateOnlyBastion set to true) does not require a public IP.networkAcls property with ipRules to specify allowed IP address prefixes (e.g., 1.1.1.1/16).Immutability & Limitations
bastionHostName is immutable and cannot be changed after creation. Changing it forces resource replacement.bastionHostName or resourceGroupName (both immutable) will force Pulumi to destroy and recreate the Bastion Host resource.Scaling & Availability
scaleUnits property to specify the number of scale units for the Bastion Host resource.zones property to specify a list of availability zones where the resource should be deployed.