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 for internet-facing domains, subdomain delegation with NS records, and private zones for VPC-internal DNS resolution.
Hosted zones are containers for DNS records. Private zones require VPCs with DNS support enabled; subdomain zones require parent zones and delegation records. 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 for a domain 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 specifies the domain this zone manages. Route53 automatically creates NS and SOA records. You’ll use the zone’s nameServers output to update your domain registrar’s name server configuration, directing DNS queries to Route53.
Delegate a subdomain to its own hosted zone
Organizations often 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 child zone for dev.example.com. The NS record in the parent zone delegates queries for dev.example.com to the child zone’s name servers. The nameServers output from the child zone provides the values for the NS record’s records property.
Create a private zone for VPC-internal DNS
Applications running in VPCs often need internal DNS resolution that isn’t exposed to 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}
Private zones respond only to queries from associated VPCs. The vpcs property lists VPC IDs where this zone resolves. 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 with NS records, and VPC associations for private DNS. 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) and parent zones (for subdomain delegation). They focus on configuring the zone rather than provisioning all DNS records.
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 and aws.route53.ZoneAssociation resources for the same zone simultaneously. Choose one approach or use ignoreChanges on the vpcs property to manage additional associations via aws.route53.ZoneAssociation.vpcs array.vpcs property with at least one VPC ID. The VPC must have DNS support and DNS hostnames enabled.Subdomain Configuration
dev.example.com), then create an NS record in the parent zone pointing to the subdomain zone’s nameServers.Zone Properties & Lifecycle
name and delegationSetId properties are immutable and cannot be changed after zone creation.forceDestroy to true to automatically delete all records (including those managed outside Pulumi) when destroying the zone.enableAcceleratedRecovery to false. Simply removing the property from your configuration won’t disable it.Delegation Sets
delegationSetId conflicts with vpc because delegation sets can only be used for public zones.Using a different cloud?
Explore networking guides for other cloud providers: