The azure-native:network:BastionHost resource, part of the Pulumi Azure Native provider, provisions an Azure Bastion service that provides secure RDP and SSH connectivity to virtual machines through the Azure portal. This guide focuses on three capabilities: standard deployment with public access, private-only mode for isolated networks, and Developer SKU with IP-based access control.
Bastion hosts require a virtual network with a subnet named ‘BastionHostSubnet’ and, for standard deployments, a public IP address. The examples are intentionally small. Combine them with your own virtual networks, public IPs, and access policies.
Deploy a standard Bastion Host with public access
Most deployments provide browser-based VM access without exposing public IPs on individual VMs.
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 connects Bastion to your network. The publicIPAddress property assigns a public IP for inbound connections, while the subnet property must reference a subnet named exactly “BastionHostSubnet”. Azure uses this dedicated subnet to deploy the Bastion service infrastructure.
Deploy a private-only Bastion Host without public IP
Organizations with strict isolation requirements can restrict Bastion access to users connected via ExpressRoute or VPN.
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. The ipConfigurations array still references the BastionHostSubnet, but users must connect through private network paths. This mode prevents internet-based access entirely.
Deploy a Developer SKU Bastion with network ACLs
The Developer SKU reduces costs in non-production environments while adding IP-based access control.
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 virtualNetwork property references an existing VNet by ID. The networkAcls property defines IP allowlists; only source IPs matching the addressPrefix can access the Bastion service. The empty ipConfigurations array indicates this uses the Developer SKU, which doesn’t require explicit IP configuration.
Beyond these examples
These snippets focus on specific Bastion Host features: standard and private-only deployment modes, and network ACLs for IP-based access control. They’re intentionally minimal rather than full remote access 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, enableKerberos)
- SKU selection and scale units (sku, scaleUnits)
- Availability zones (zones)
- Session recording and copy/paste controls
These omissions are intentional: the goal is to illustrate how each Bastion feature is wired, not provide drop-in remote access modules. See the BastionHost 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, while Developer Bastion uses an empty ipConfigurations array and requires a virtualNetwork reference instead.ipConfigurations with a subnet (named BastionHostSubnet) and a publicIPAddress reference pointing to an existing public IP resource.ipConfigurations to an empty array, configure virtualNetwork to reference an existing VNet, and optionally add networkAcls for IP-based access control.enablePrivateOnlyBastion to true when you don’t need a public IP address. In this mode, configure ipConfigurations with only a subnet reference.Networking & Subnet Requirements
BastionHostSubnet, as shown in all deployment examples.networkAcls with ipRules containing addressPrefix values to restrict access by IP range (shown in Developer Bastion example).Features & Capabilities
enableFileCopy), IP Connect (enableIpConnect), Kerberos (enableKerberos), Session Recording (enableSessionRecording), Shareable Link (enableShareableLink), and Tunneling (enableTunneling). All features default to false. You can also disable Copy/Paste with disableCopyPaste.Resource Management
bastionHostName is immutable and cannot be changed after resource creation. Changing it will force resource replacement.zones property to specify a list of availability zones where the resource should be deployed.