The aws:route53/zone:Zone resource, part of the Pulumi AWS provider, defines a Route53 hosted zone that contains DNS records for a domain or subdomain. This guide focuses on three capabilities: public zone creation, subdomain delegation, and private zone VPC associations.
Public zones operate independently and respond to queries from any internet resolver. Private zones require VPCs with DNS support enabled and only resolve queries from associated VPCs. Subdomain delegation requires creating NS records in parent zones. The examples are intentionally small. Combine them with your own Record resources and VPC infrastructure.
Create a public zone for internet-facing DNS
Most deployments start with a public hosted zone that manages DNS records accessible from the internet.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const primary = new aws.route53.Zone("primary", {name: "example.com"});
import pulumi
import pulumi_aws as aws
primary = aws.route53.Zone("primary", name="example.com")
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/route53"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
_, err := route53.NewZone(ctx, "primary", &route53.ZoneArgs{
Name: pulumi.String("example.com"),
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var primary = new Aws.Route53.Zone("primary", new()
{
Name = "example.com",
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.route53.Zone;
import com.pulumi.aws.route53.ZoneArgs;
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 primary = new Zone("primary", ZoneArgs.builder()
.name("example.com")
.build());
}
}
resources:
primary:
type: aws:route53:Zone
properties:
name: example.com
The name property sets the domain name for the zone. Route53 automatically assigns name servers that respond to DNS queries. You’ll use the zone’s nameServers output when updating your domain registrar’s configuration.
Delegate a subdomain to its own zone
Organizations split DNS management by creating separate zones for subdomains, allowing different teams to manage records independently.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const main = new aws.route53.Zone("main", {name: "example.com"});
const dev = new aws.route53.Zone("dev", {
name: "dev.example.com",
tags: {
Environment: "dev",
},
});
const dev_ns = new aws.route53.Record("dev-ns", {
zoneId: main.zoneId,
name: "dev.example.com",
type: aws.route53.RecordType.NS,
ttl: 30,
records: dev.nameServers,
});
import pulumi
import pulumi_aws as aws
main = aws.route53.Zone("main", name="example.com")
dev = aws.route53.Zone("dev",
name="dev.example.com",
tags={
"Environment": "dev",
})
dev_ns = aws.route53.Record("dev-ns",
zone_id=main.zone_id,
name="dev.example.com",
type=aws.route53.RecordType.NS,
ttl=30,
records=dev.name_servers)
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/route53"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
main, err := route53.NewZone(ctx, "main", &route53.ZoneArgs{
Name: pulumi.String("example.com"),
})
if err != nil {
return err
}
dev, err := route53.NewZone(ctx, "dev", &route53.ZoneArgs{
Name: pulumi.String("dev.example.com"),
Tags: pulumi.StringMap{
"Environment": pulumi.String("dev"),
},
})
if err != nil {
return err
}
_, err = route53.NewRecord(ctx, "dev-ns", &route53.RecordArgs{
ZoneId: main.ZoneId,
Name: pulumi.String("dev.example.com"),
Type: pulumi.String(route53.RecordTypeNS),
Ttl: pulumi.Int(30),
Records: dev.NameServers,
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var main = new Aws.Route53.Zone("main", new()
{
Name = "example.com",
});
var dev = new Aws.Route53.Zone("dev", new()
{
Name = "dev.example.com",
Tags =
{
{ "Environment", "dev" },
},
});
var dev_ns = new Aws.Route53.Record("dev-ns", new()
{
ZoneId = main.ZoneId,
Name = "dev.example.com",
Type = Aws.Route53.RecordType.NS,
Ttl = 30,
Records = dev.NameServers,
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.route53.Zone;
import com.pulumi.aws.route53.ZoneArgs;
import com.pulumi.aws.route53.Record;
import com.pulumi.aws.route53.RecordArgs;
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 main = new Zone("main", ZoneArgs.builder()
.name("example.com")
.build());
var dev = new Zone("dev", ZoneArgs.builder()
.name("dev.example.com")
.tags(Map.of("Environment", "dev"))
.build());
var dev_ns = new Record("dev-ns", RecordArgs.builder()
.zoneId(main.zoneId())
.name("dev.example.com")
.type("NS")
.ttl(30)
.records(dev.nameServers())
.build());
}
}
resources:
main:
type: aws:route53:Zone
properties:
name: example.com
dev:
type: aws:route53:Zone
properties:
name: dev.example.com
tags:
Environment: dev
dev-ns:
type: aws:route53:Record
properties:
zoneId: ${main.zoneId}
name: dev.example.com
type: NS
ttl: '30'
records: ${dev.nameServers}
This configuration creates two zones: a parent zone for example.com and a subdomain zone for dev.example.com. The NS record in the parent zone delegates queries for dev.example.com to the subdomain zone’s name servers. The nameServers output from the subdomain zone populates the NS record’s records property, establishing the delegation chain.
Create a private zone for VPC-internal DNS
Applications in VPCs often need internal DNS names that aren’t resolvable from the internet.
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const primary = new aws.ec2.Vpc("primary", {
cidrBlock: "10.6.0.0/16",
enableDnsHostnames: true,
enableDnsSupport: true,
});
const secondary = new aws.ec2.Vpc("secondary", {
cidrBlock: "10.7.0.0/16",
enableDnsHostnames: true,
enableDnsSupport: true,
});
const _private = new aws.route53.Zone("private", {
name: "example.com",
vpcs: [
{
vpcId: primary.id,
},
{
vpcId: secondary.id,
},
],
});
import pulumi
import pulumi_aws as aws
primary = aws.ec2.Vpc("primary",
cidr_block="10.6.0.0/16",
enable_dns_hostnames=True,
enable_dns_support=True)
secondary = aws.ec2.Vpc("secondary",
cidr_block="10.7.0.0/16",
enable_dns_hostnames=True,
enable_dns_support=True)
private = aws.route53.Zone("private",
name="example.com",
vpcs=[
{
"vpc_id": primary.id,
},
{
"vpc_id": secondary.id,
},
])
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/ec2"
"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/route53"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
primary, err := ec2.NewVpc(ctx, "primary", &ec2.VpcArgs{
CidrBlock: pulumi.String("10.6.0.0/16"),
EnableDnsHostnames: pulumi.Bool(true),
EnableDnsSupport: pulumi.Bool(true),
})
if err != nil {
return err
}
secondary, err := ec2.NewVpc(ctx, "secondary", &ec2.VpcArgs{
CidrBlock: pulumi.String("10.7.0.0/16"),
EnableDnsHostnames: pulumi.Bool(true),
EnableDnsSupport: pulumi.Bool(true),
})
if err != nil {
return err
}
_, err = route53.NewZone(ctx, "private", &route53.ZoneArgs{
Name: pulumi.String("example.com"),
Vpcs: route53.ZoneVpcArray{
&route53.ZoneVpcArgs{
VpcId: primary.ID(),
},
&route53.ZoneVpcArgs{
VpcId: secondary.ID(),
},
},
})
if err != nil {
return err
}
return nil
})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;
return await Deployment.RunAsync(() =>
{
var primary = new Aws.Ec2.Vpc("primary", new()
{
CidrBlock = "10.6.0.0/16",
EnableDnsHostnames = true,
EnableDnsSupport = true,
});
var secondary = new Aws.Ec2.Vpc("secondary", new()
{
CidrBlock = "10.7.0.0/16",
EnableDnsHostnames = true,
EnableDnsSupport = true,
});
var @private = new Aws.Route53.Zone("private", new()
{
Name = "example.com",
Vpcs = new[]
{
new Aws.Route53.Inputs.ZoneVpcArgs
{
VpcId = primary.Id,
},
new Aws.Route53.Inputs.ZoneVpcArgs
{
VpcId = secondary.Id,
},
},
});
});
package generated_program;
import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.ec2.Vpc;
import com.pulumi.aws.ec2.VpcArgs;
import com.pulumi.aws.route53.Zone;
import com.pulumi.aws.route53.ZoneArgs;
import com.pulumi.aws.route53.inputs.ZoneVpcArgs;
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 primary = new Vpc("primary", VpcArgs.builder()
.cidrBlock("10.6.0.0/16")
.enableDnsHostnames(true)
.enableDnsSupport(true)
.build());
var secondary = new Vpc("secondary", VpcArgs.builder()
.cidrBlock("10.7.0.0/16")
.enableDnsHostnames(true)
.enableDnsSupport(true)
.build());
var private_ = new Zone("private", ZoneArgs.builder()
.name("example.com")
.vpcs(
ZoneVpcArgs.builder()
.vpcId(primary.id())
.build(),
ZoneVpcArgs.builder()
.vpcId(secondary.id())
.build())
.build());
}
}
resources:
primary:
type: aws:ec2:Vpc
properties:
cidrBlock: 10.6.0.0/16
enableDnsHostnames: true
enableDnsSupport: true
secondary:
type: aws:ec2:Vpc
properties:
cidrBlock: 10.7.0.0/16
enableDnsHostnames: true
enableDnsSupport: true
private:
type: aws:route53:Zone
properties:
name: example.com
vpcs:
- vpcId: ${primary.id}
- vpcId: ${secondary.id}
The vpcs property associates the zone with one or more VPCs, making DNS records resolvable only within those VPCs. Each VPC must have enableDnsHostnames and enableDnsSupport set to true. Private zones conflict with delegationSetId since delegation sets only work for public zones.
Beyond these examples
These snippets focus on specific zone-level features: public and private zone creation, subdomain delegation, and VPC associations. They’re intentionally minimal rather than full DNS configurations.
The examples may reference pre-existing infrastructure such as VPCs with DNS support enabled for private zones. They focus on configuring the zone rather than provisioning DNS records or VPC infrastructure.
To keep things focused, common zone patterns are omitted, including:
- Reusable delegation sets (delegationSetId)
- Force destroy for zones with records (forceDestroy)
- Accelerated recovery configuration (enableAcceleratedRecovery)
- DNSSEC configuration (KeySigningKey, HostedZoneDnsSec resources)
These omissions are intentional: the goal is to illustrate how each zone feature is wired, not provide drop-in DNS modules. See the Route53 Zone resource reference for all available configuration options.
Let's create AWS Route53 Hosted Zones
Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.
Try Pulumi Cloud for FREEFrequently Asked Questions
VPC & Private Zones
vpc blocks with separate aws.route53.ZoneAssociation resources for the same zone causes perpetual plan differences. Choose one approach: either manage all VPC associations inline using vpc blocks, or use aws.route53.ZoneAssociation resources exclusively. You can use ignoreChanges on the vpcs property if you need to manage additional associations separately.vpcs array when creating a private zone.vpcs array, as shown in the private zone example with primary and secondary VPCs.Subdomain Configuration
dev.example.com), then create an aws.route53.Record of type NS in the parent zone (example.com) that points to the subdomain zone’s name servers. Both resources are required for proper DNS delegation.Zone Properties & Behavior
name property is immutable and cannot be changed after the zone is created.enableAcceleratedRecovery to false. Simply removing the property from your configuration won’t disable it once it’s been enabled.forceDestroy to true to automatically delete all records (including those managed outside Pulumi) when destroying the zone.Public vs Private Zones
delegationSetId) are used for public zones to assign specific name servers, while VPC associations (via vpcs) are used for private zones to make DNS resolution available within VPCs. These two features conflict with each other: delegation sets can only be used for public zones.delegationSetId property conflicts with vpcs because delegation sets can only be used for public zones.Using a different cloud?
Explore networking guides for other cloud providers: