diff --git a/compute/limitations-and-roadmap.mdx b/compute/limitations-and-roadmap.mdx
index 90b8933..8d7ae71 100644
--- a/compute/limitations-and-roadmap.mdx
+++ b/compute/limitations-and-roadmap.mdx
@@ -34,7 +34,6 @@ The Compute preview has the following limitations. For limitations of each [runt
- During the preview, Instances have private IPv6 addresses only. Private IPv4 addresses and public IP addresses are a preview limitation.
- Instances reach IPv4-only internet services by hostname only, through DNS64 and NAT64. A connection to an IPv4 address fails. See [Reach the internet](/galactic-vpc/networks/traffic#reach-the-internet).
- Every Instance can reach the internet, and you can't turn that off. Outbound traffic leaves from shared addresses that Datum doesn't publish, so you can't allowlist your Instances at a destination.
-- Private DNS names for Instances are on the [roadmap](/galactic-vpc/limitations-and-roadmap#planned-capabilities).
- Firewall rules to filter traffic between Instances on a network are on the [roadmap](/galactic-vpc/limitations-and-roadmap#planned-capabilities).
- Public access is HTTP and HTTPS only, served through Datum's [ALB](/alb/overview). Raw TCP and UDP exposure is a preview limitation.
diff --git a/docs.json b/docs.json
index d885228..963a3f6 100644
--- a/docs.json
+++ b/docs.json
@@ -213,6 +213,9 @@
"domain-dns/domains",
"domain-dns/dns",
"domain-dns/private-dns",
+ "domain-dns/private-dns-quickstart",
+ "domain-dns/private-dns-zones",
+ "domain-dns/private-dns-reference",
"domain-dns/operations",
"domain-dns/point-to-alb",
"domain-dns/observability",
diff --git a/domain-dns/limitations-and-roadmap.mdx b/domain-dns/limitations-and-roadmap.mdx
index 1736fa7..947552e 100644
--- a/domain-dns/limitations-and-roadmap.mdx
+++ b/domain-dns/limitations-and-roadmap.mdx
@@ -13,6 +13,7 @@ Domains and DNS are still being built. This page lists known limitations and whe
- **A zone's domain name can't change.** To use a different domain, create a new zone.
- **One zone for each domain.** A domain can belong to only one zone on Datum.
- **`ALIAS` isn't portable.** `ALIAS` isn't a standard record type, so a zone file exported from Datum can't carry it to another provider. See [ALIAS](/domain-dns/dns#alias-/-cname-flattening).
+- **Private DNS has no `datumctl dns` or Portal editing yet.** Manage private zones, associations, and records with `datumctl apply`. See [Manage private zones and records](/domain-dns/private-dns-zones).
- **The DNS API is `v1alpha1`.** Field names in `DNSZone` and `DNSRecordSet` can change between releases. The `datumctl dns` plugin keeps up with those changes for you.
## Roadmap
diff --git a/domain-dns/private-dns-quickstart.mdx b/domain-dns/private-dns-quickstart.mdx
new file mode 100644
index 0000000..19d2a69
--- /dev/null
+++ b/domain-dns/private-dns-quickstart.mdx
@@ -0,0 +1,178 @@
+---
+title: "Private DNS quickstart"
+sidebarTitle: "Private DNS quickstart"
+description: "Deploy two workloads on a gVPC network and reach one from the other by its private name."
+---
+
+This quickstart deploys two small workloads on the same [gVPC](/galactic-vpc/overview) network. From the first, you look up the second by its [Private DNS](/domain-dns/private-dns) name and connect to it. Then you give it a name of your own. It takes about ten minutes.
+
+## Before you begin
+
+- Install `datumctl` and the `compute` plugin, and select a project. See [Compute commands](/datumctl/compute/overview).
+- Your account needs the **Compute Admin**, **Network Admin**, and **DNS Admin** roles in the project.
+
+## Reach an Instance by name
+
+
+
+ Deploy an `nginx` workload named `web` to Dallas:
+
+ ```bash
+ datumctl compute deploy web \
+ --image=docker.io/library/nginx:1.27-alpine \
+ --runtime-class=general-purpose \
+ --location=us-central-1
+ ```
+
+ If this is the first deploy in the project, the command asks whether to create a network named `default`. Enter `y`. Then enter `y` to apply the plan.
+
+
+
+ Deploy a second workload named `client` on the same network. It uses the same image, because the image includes `nslookup` and `wget`:
+
+ ```bash
+ datumctl compute deploy client \
+ --image=docker.io/library/nginx:1.27-alpine \
+ --runtime-class=general-purpose \
+ --location=us-central-1
+ ```
+
+
+
+ Compute publishes each Instance at `.instances.datum.internal`. Read the exact name from the Instance:
+
+ ```bash
+ datumctl compute instances --workload=web
+ datumctl get instance web-default-us-central-1-0 \
+ -o jsonpath='{.status.dns[0].hostnames[0]}'
+ ```
+
+ The output is similar to the following:
+
+ ```text
+ web-default-us-central-1-0.instances.datum.internal
+ ```
+
+ If the output is empty, wait a few seconds and run the command again. The name appears once the Instance is ready.
+
+
+
+ Open a shell in the client and look up the web server:
+
+ ```bash
+ datumctl compute exec client-default-us-central-1-0 -- \
+ nslookup -type=AAAA web-default-us-central-1-0.instances.datum.internal
+ ```
+
+ The answer is the web server's private IPv6 address. Note it for later. Now connect to it by name:
+
+ ```bash
+ datumctl compute exec client-default-us-central-1-0 -- \
+ wget -qO- http://web-default-us-central-1-0.instances.datum.internal/
+ ```
+
+ The output is the `nginx` welcome page.
+
+
+
+## Give the web server your own name
+
+To give the web server a name you choose, create a private zone of your own, attach it to the network, and add a record.
+
+
+
+ Each network has one DNS context. To list them with the network each one belongs to, run:
+
+ ```bash
+ datumctl get dnsresolvercontexts \
+ -o custom-columns='CONTEXT:.metadata.name,NETWORK:.metadata.ownerReferences[0].name'
+ ```
+
+ Note the `CONTEXT` for the `default` network, such as `dns-default-4f1c2a9e7b30`.
+
+
+
+ Save the following as `private-zone.yaml`. Replace `CONTEXT_NAME` with the context from the previous step, and `WEB_ADDRESS` with the web server's address from the lookup:
+
+ ```yaml
+ apiVersion: dns.networking.miloapis.com/v1alpha1
+ kind: DNSZone
+ metadata:
+ name: corp-internal
+ spec:
+ domainName: corp.internal
+ visibility: Private
+ ---
+ apiVersion: dns.networking.miloapis.com/v1alpha1
+ kind: DNSZoneAssociation
+ metadata:
+ name: corp-internal-default
+ spec:
+ dnsZoneRef:
+ name: corp-internal
+ resolverContextRef:
+ name: CONTEXT_NAME
+ ---
+ apiVersion: dns.networking.miloapis.com/v1alpha1
+ kind: DNSRecordSet
+ metadata:
+ name: www
+ spec:
+ dnsZoneRef:
+ name: corp-internal
+ recordType: AAAA
+ records:
+ - name: www
+ ttl: 60
+ aaaa:
+ content: WEB_ADDRESS
+ ```
+
+ Apply it:
+
+ ```bash
+ datumctl apply -f private-zone.yaml
+ ```
+
+
+
+ Check that the association and record are accepted, and that the zone is programmed:
+
+ ```bash
+ datumctl get dnszoneassociation corp-internal-default \
+ -o jsonpath='{.status.conditions[?(@.type=="Accepted")].status}'
+ datumctl get dnsrecordset www \
+ -o jsonpath='{.status.conditions[?(@.type=="Accepted")].status}'
+ datumctl get dnszone corp-internal
+ ```
+
+ Continue when both commands print `True` and the zone's `PROGRAMMED` column shows `True`. This usually takes less than a minute. If something stays `False`, see [Troubleshooting](/domain-dns/troubleshooting#private-dns).
+
+
+
+ ```bash
+ datumctl compute exec client-default-us-central-1-0 -- \
+ wget -qO- http://www.corp.internal/
+ ```
+
+ The output is the same welcome page. The name resolves only from the `default` network.
+
+ This record holds an address, so update it if Compute replaces the web server's Instance. For names that follow an Instance, see [Find services by name](/domain-dns/private-dns-zones#find-services-by-name).
+
+
+
+## Clean up
+
+```bash
+datumctl delete -f private-zone.yaml
+datumctl compute destroy client
+datumctl compute destroy web
+```
+
+Deleting the workloads removes their Instance names.
+
+## What's next
+
+- [Manage private zones and records](/domain-dns/private-dns-zones)
+- [Private DNS limits and behavior](/domain-dns/private-dns-reference)
+- [Private DNS for Compute](/compute/private-dns)
diff --git a/domain-dns/private-dns-reference.mdx b/domain-dns/private-dns-reference.mdx
new file mode 100644
index 0000000..ca659c4
--- /dev/null
+++ b/domain-dns/private-dns-reference.mdx
@@ -0,0 +1,71 @@
+---
+title: "Private DNS limits and behavior"
+sidebarTitle: "Private DNS limits"
+description: "Where Private DNS is available, naming rules, limits, and how long changes take."
+---
+
+This page lists the rules and limits for [Private DNS](/domain-dns/private-dns).
+
+
+ Private DNS is in preview, and the `v1alpha1` API can change.
+
+
+## Locations and runtimes
+
+Private DNS works on every [gVPC](/galactic-vpc/overview) network, in these locations:
+
+| Location | City |
+| --- | --- |
+| `us-central-1` | Dallas |
+| `us-east-1` | Ashburn |
+| `us-west-1` | San Jose |
+
+A network that spans several locations has one set of names. An Instance in one location resolves the names of Instances in the other locations on the same network.
+
+Both [runtime classes](/compute/runtimes/overview), `general-purpose` and `unikernel`, get Private DNS. Instances use the resolvers that Datum configures for them. In a `general-purpose` Instance, they're in `/etc/resolv.conf`. An app that uses its own resolver, such as a hard-coded public resolver or DNS over HTTPS, can't resolve private names.
+
+## Names
+
+- Every network's managed zone is `datum.internal`. Each network has its own copy.
+- Compute publishes Instances at `.instances.datum.internal`. Long or unusual Instance names are shortened, so read the exact name from the Instance's `status.dns`. Compute owns every name at or below `instances.datum.internal`, so you can't add records there or create a zone that covers it.
+- Use full names. Don't rely on short names such as `web-default-us-central-1-0`: they depend on search domains that can differ between Instances.
+- Zone domains use lowercase letters, digits, and hyphens, with each label starting and ending with a letter or digit. They have at least two labels and are at most 253 characters long.
+- Record names are relative to their zone, such as `api`, or fully qualified. `@` means the zone's own domain. Names aren't case-sensitive.
+- A private zone hides the public records of the same domain from the networks it's associated with.
+
+## Record rules
+
+- `ALIAS` and `SOA` records aren't supported. Datum manages each private zone's `SOA` record.
+- A zone's own domain (`@`) can't be a `CNAME`.
+- A `CNAME` can't share a name with another record type.
+- Point a `CNAME` at a name in the same zone. A `CNAME` that points into another zone, or at a public name, may return only the `CNAME`, which most apps can't follow.
+- An `SRV` target must be a name with `A` or `AAAA` records in the same zone.
+- If two record sets have the same name and type, the older one keeps it, and the newer one isn't accepted.
+- A record set is accepted or rejected as a whole. One invalid record keeps every name in it from resolving.
+
+## Limits
+
+| Limit | Value |
+| --- | --- |
+| Private zones a network can resolve, including `datum.internal` | 256. Keep to this or fewer: a 257th association isn't refused yet, but the network then stops picking up DNS changes. |
+| Zone domain length | 253 characters |
+| Default TTL of a record you create | 300 seconds |
+| TTL of an Instance name | 30 seconds |
+
+## How long changes take
+
+| Change | When it takes effect |
+| --- | --- |
+| New Instance | Its name usually resolves within about 30 seconds of the Instance becoming ready. |
+| New record, zone, or association | Usually within a minute of being accepted, and longer when a project changes many names at once. The zone's `Programmed` condition says when. |
+| Changed or removed record | New lookups get the new answer once the zone is programmed. Clients that cached the old answer keep it until its TTL expires. |
+| Deleted or unready Instance | Its addresses are withdrawn. Clients can keep the old answer for up to 30 seconds. |
+
+Publication is asynchronous. Wait for the status conditions described in [Check status](/domain-dns/private-dns-zones#check-status) rather than a fixed delay.
+
+## Privacy
+
+- A private name resolves only from networks that its zone is associated with. Private names aren't published to public DNS.
+- A network resolves another network's zone only through an association that you create.
+- Resolving a name doesn't grant network access. See [Traffic and connecting](/galactic-vpc/networks/traffic).
+- Public names keep resolving through the same resolvers.
diff --git a/domain-dns/private-dns-zones.mdx b/domain-dns/private-dns-zones.mdx
new file mode 100644
index 0000000..a21ca21
--- /dev/null
+++ b/domain-dns/private-dns-zones.mdx
@@ -0,0 +1,166 @@
+---
+title: "Manage private zones and records"
+sidebarTitle: "Private zones and records"
+description: "Add your own private names, choose which gVPC networks can resolve them, and find services by name."
+---
+
+[Private DNS](/domain-dns/private-dns) publishes Instance names for you. This page covers the names you manage yourself: records in a network's `datum.internal` zone, private zones of your own, and which networks can resolve them.
+
+Manage these resources with `datumctl apply`, `datumctl get`, and `datumctl delete`. Your account needs the **DNS Admin** role to change them, and **DNS Viewer** to read them.
+
+## How the pieces fit
+
+| Resource | What it is |
+| --- | --- |
+| DNS context | One per network, created for you. It decides which private zones the network can resolve. |
+| Managed zone | The network's `datum.internal` zone, created for you. Compute publishes Instance names in it. |
+| Private zone | A `DNSZone` with `visibility: Private` that you create, such as `corp.internal`. |
+| Zone association | A `DNSZoneAssociation` that lets one network resolve one private zone. |
+| Record set | A `DNSRecordSet` that holds records in a zone. |
+
+A private zone resolves only from the networks it's associated with. Two networks can each have a zone with the same domain, and each gets its own answers.
+
+## Find a network's DNS context and zone
+
+To list each network's DNS context and managed zone, run:
+
+```bash
+datumctl get dnsresolvercontexts \
+ -o custom-columns='CONTEXT:.metadata.name,NETWORK:.metadata.ownerReferences[0].name,ZONE:.status.managedNamespace.dnsZoneRef.name'
+```
+
+The output is similar to the following:
+
+```text
+CONTEXT NETWORK ZONE
+dns-default-4f1c2a9e7b30 default managed-8d03b6e1f2a4
+```
+
+Use `CONTEXT` when you associate a zone with the network, and `ZONE` when you add records to the network's `datum.internal` zone.
+
+## Add a record to datum.internal
+
+To add a name to one network without creating a zone, add a record set to the network's managed zone. Replace `MANAGED_ZONE` with the `ZONE` from the previous section:
+
+```yaml
+apiVersion: dns.networking.miloapis.com/v1alpha1
+kind: DNSRecordSet
+metadata:
+ name: db
+spec:
+ dnsZoneRef:
+ name: MANAGED_ZONE
+ recordType: AAAA
+ records:
+ - name: db
+ ttl: 60
+ aaaa:
+ content: fd20:1::20
+```
+
+`db.datum.internal` now resolves on that network only. You can't add records at or below `instances.datum.internal`, which Compute owns.
+
+## Create a private zone
+
+Create a zone when you want a namespace of your own, or one that several networks share:
+
+```yaml
+apiVersion: dns.networking.miloapis.com/v1alpha1
+kind: DNSZone
+metadata:
+ name: corp-internal
+spec:
+ domainName: corp.internal
+ visibility: Private
+```
+
+Keep the following points in mind:
+
+- You can't change a zone's domain or visibility after you create it. Create a new zone instead.
+- A private zone resolves nowhere until you associate it with a network.
+- Prefer a domain you control, or one under `.internal`. If you create a private zone for a public domain, such as `example.com`, associated networks get your private answers instead of the public ones for every name in that domain.
+
+## Choose which networks resolve a zone
+
+Associate the zone with each network that should resolve it:
+
+```yaml
+apiVersion: dns.networking.miloapis.com/v1alpha1
+kind: DNSZoneAssociation
+metadata:
+ name: corp-internal-default
+spec:
+ dnsZoneRef:
+ name: corp-internal
+ resolverContextRef:
+ name: dns-default-4f1c2a9e7b30
+```
+
+To share the zone with another network, create another association that names that network's context. To stop a network from resolving the zone, delete its association. The zone and its records stay.
+
+An association can't be edited to point at a different zone or network. Delete it and create a new one. If you delete and recreate a zone with the same name, also recreate its associations.
+
+A network can't have two associated zones with the same domain. A zone can sit below another associated zone, such as `db.corp.internal` below `corp.internal`. Lookups use the zone with the longest matching domain.
+
+## Add records to a private zone
+
+Records in a private zone use the same `DNSRecordSet` resource as [public DNS](/domain-dns/dns):
+
+```yaml
+apiVersion: dns.networking.miloapis.com/v1alpha1
+kind: DNSRecordSet
+metadata:
+ name: corp-api
+spec:
+ dnsZoneRef:
+ name: corp-internal
+ recordType: AAAA
+ records:
+ - name: api
+ ttl: 60
+ aaaa:
+ content: fd20:1::5
+ - name: api
+ ttl: 60
+ aaaa:
+ content: fd20:1::6
+```
+
+Private zones support `A`, `AAAA`, `CNAME`, `TXT`, `MX`, `SRV`, `CAA`, `NS`, `PTR`, `TLSA`, `HTTPS`, and `SVCB` records. For the rules that apply, see [Private DNS limits and behavior](/domain-dns/private-dns-reference#record-rules).
+
+
+ Instances have IPv6 addresses only. Use `AAAA` records for addresses on your network.
+
+
+## Find services by name
+
+Use one of the following, depending on what you need:
+
+- **One Instance:** use its Compute name, such as `web-default-us-central-1-0.instances.datum.internal`. Read it from the Instance's `status.dns`. See [Private DNS for Compute](/compute/private-dns).
+- **A stable name for a service:** add a `CNAME` to the network's `datum.internal` zone that points at an Instance name, such as `api.datum.internal` → `web-default-us-central-1-0.instances.datum.internal.`. Update the `CNAME` when you replace the Instance.
+- **Several Instances behind one name:** create `AAAA` records for each address under one name, as in the example above. Clients pick an address from the answer. DNS doesn't check whether each address is healthy.
+
+## Check status
+
+A change goes through two steps. First the record set or association is accepted. Then the zone is programmed, and the name resolves.
+
+```bash
+datumctl get dnsrecordset corp-api \
+ -o jsonpath='{.status.conditions[?(@.type=="Accepted")].reason}'
+datumctl get dnszone corp-internal
+```
+
+| Where | Condition | What it means |
+| --- | --- | --- |
+| Record set, association | `Accepted` | The change is valid. If it's `False`, the reason says why. |
+| Zone | `Programmed` | The zone's latest records resolve from at least one resolver. |
+| Zone | `Published` | Every resolver the zone needs has the latest records. |
+
+For what each reason means, see [Troubleshooting](/domain-dns/troubleshooting#private-dns).
+
+## Delete names and zones
+
+- Delete a record set to remove its names. Clients can keep an old answer until its TTL expires.
+- Delete a zone association to stop one network from resolving the zone.
+- Delete a zone to remove it from every network. Delete its record sets and associations too.
+- Don't delete a network's managed zone or its association. Instance names on that network stop resolving. If you deleted one by mistake, contact [support@datum.net](mailto:support@datum.net).
diff --git a/domain-dns/troubleshooting.mdx b/domain-dns/troubleshooting.mdx
index b42877d..39ada65 100644
--- a/domain-dns/troubleshooting.mdx
+++ b/domain-dns/troubleshooting.mdx
@@ -75,3 +75,47 @@ If you use [ExternalDNS](/domain-dns/operations#automate-records-with-externaldn
- The webhook needs permission to read `dnszones` and to manage `dnsrecordsets` in your project.
For the webhook's metrics, see [Observability](/domain-dns/observability#externaldns-webhook-metrics).
+
+## Private DNS
+
+These checks apply to [Private DNS](/domain-dns/private-dns) names, which resolve only inside a gVPC network.
+
+### A private name doesn't resolve
+
+Work through these checks in order, from an Instance on the network:
+
+1. **Use the full name.** Look up `web-default-us-central-1-0.instances.datum.internal`, not `web-default-us-central-1-0`. Short names depend on search domains.
+2. **Check the network.** A private name resolves only from networks its zone is associated with. An Instance name resolves only from the Instance's own network.
+3. **Check the resolver.** Run `cat /etc/resolv.conf` in a `general-purpose` Instance. The app must use these resolvers. An app that uses its own resolver, such as DNS over HTTPS or a hard-coded public resolver, can't resolve private names.
+4. **Check the Instance.** An Instance name exists only while the Instance is ready. Run `datumctl get instance INSTANCE_NAME -o yaml` and check `status.dns`. If its condition isn't `Ready`, the reason says why.
+5. **Check the zone.** Run `datumctl get dnszone ZONE_NAME`. If `PROGRAMMED` isn't `True`, wait a minute and check again. A zone that isn't associated with any network shows no status.
+6. **Check the record or association.** See the next section.
+7. **Check `CNAME` targets.** A `CNAME` that points into another zone may return only the `CNAME`. Point it at a name in the same zone, or use `AAAA` records.
+
+### A record set or association isn't accepted
+
+Read the reason:
+
+```bash
+datumctl get dnsrecordset RECORDSET_NAME \
+ -o jsonpath='{.status.conditions[?(@.type=="Accepted")].reason}'
+datumctl get dnszoneassociation ASSOCIATION_NAME \
+ -o jsonpath='{.status.conditions[?(@.type=="Accepted")].reason}'
+```
+
+| Reason | What it means | What to do |
+| --- | --- | --- |
+| `UnsupportedPrivateRecord` | The record set uses a type or name that private zones don't support, such as `ALIAS`, `SOA`, or a `CNAME` at the zone's own domain. | Use a supported record. See [Record rules](/domain-dns/private-dns-reference#record-rules). |
+| `ReservedScope` | The name is at or below a name that a Datum service owns, such as `instances.datum.internal`. | Use a different name. |
+| `NameConflict` | An older record set already has this name and type, or a `CNAME` shares the name with another type. | Change the record in the record set that has it, or delete that record set first. |
+| `MissingSRVTargetAddress` | An `SRV` target has no `A` or `AAAA` record in the same zone. | Add an address record for the target in the zone. |
+| `ConflictingTTL` | One name in the record set has two different TTLs. | Use one TTL for each name. |
+| `InvalidRecord`, `InvalidTTL` | A value doesn't match its record type, or the TTL is out of range. | Fix the value. |
+| `ApexConflict` | Another zone with the same domain is associated with this network. The association whose name sorts first keeps the domain. | Delete the association you don't want. |
+| `InvalidReference` | The association's zone or DNS context doesn't exist, was deleted, or was replaced by a new one with the same name. | Check both names. If they exist, delete the association and create it again. |
+
+A request can also be refused when you apply it. A message that starts with `ReservedScope:` means the same as the reason above. A `not found` error, or one that mentions `private zone lookup` or `resolver context lookup`, means the zone or DNS context you named doesn't exist in the project.
+
+### A name returns an old answer
+
+Clients and resolvers keep an answer until its TTL expires. Instance names have a 30 second TTL. Records you create default to 300 seconds. To make changes show up sooner, lower the record's TTL before you change it.
diff --git a/galactic-vpc/limitations-and-roadmap.mdx b/galactic-vpc/limitations-and-roadmap.mdx
index d0fd83c..7e65830 100644
--- a/galactic-vpc/limitations-and-roadmap.mdx
+++ b/galactic-vpc/limitations-and-roadmap.mdx
@@ -26,11 +26,9 @@ In addition to the [networking limitations that apply to all of Compute](/comput
Datum is working on the following networking capabilities:
- **Firewall rules:** Security groups that control the traffic to and from your Instances.
-- **Private DNS:** Private DNS names, so Instances can find each other without IP addresses.
- **Internet access setting:** A setting that turns internet access on or off for every Instance on a network.
-- **Datum DNS resolver:** A DNS resolver that Datum runs for Instances, which also performs DNS64.
-Outbound internet access, including IPv4 destinations, and viewing networks in the Datum Cloud portal are already available. For details, see [Reach the internet](/galactic-vpc/networks/traffic#reach-the-internet).
+Outbound internet access, including IPv4 destinations, [Private DNS](/domain-dns/private-dns) names for Instances, and viewing networks in the Datum Cloud portal are already available. For details, see [Reach the internet](/galactic-vpc/networks/traffic#reach-the-internet).
For what Datum is building next across the platform, see the [Platform roadmap](/platform/roadmap).
diff --git a/galactic-vpc/networks/traffic.mdx b/galactic-vpc/networks/traffic.mdx
index d436ee9..0b31e5a 100644
--- a/galactic-vpc/networks/traffic.mdx
+++ b/galactic-vpc/networks/traffic.mdx
@@ -21,7 +21,7 @@ The following table describes which kinds of traffic are possible:
| To or from other addresses through an Instance | No | A network only delivers a packet addressed to an Instance's own address, and only accepts a packet from an Instance's own address. An Instance can't route traffic for other addresses, so a router or VPN appliance needs an overlay, such as VXLAN or a UDP tunnel, between Instances. |
| Inbound from the internet | Through Datum's ALB | Instance addresses aren't reachable from the internet directly. Public HTTP and HTTPS traffic reaches Instances through Datum's [Application Load Balancer](/alb/overview). Raw TCP and UDP exposure is a preview limitation. |
| Outbound to the internet | Yes | Instances can reach internet destinations over IPv6, and services that have only IPv4 addresses by hostname. See [Reach the internet](#reach-the-internet). |
-| Lookups by name | Public names only | Instances resolve public DNS names. Private DNS names for Instances are on the [roadmap](/galactic-vpc/limitations-and-roadmap#planned-capabilities). Use IPv6 addresses to reach other Instances in the preview. |
+| Lookups by name | Yes | Instances resolve public names, and the [Private DNS](/domain-dns/private-dns) names of other Instances on the same network, such as `web-default-us-central-1-0.instances.datum.internal`. |
| Firewall filtering | Not in the preview | Firewall rules to filter traffic between Instances on a network are on the [roadmap](/galactic-vpc/limitations-and-roadmap#planned-capabilities). |
The workload API accepts a `networkPolicy` field on each network interface, but the field has no effect. Don't rely on it to restrict traffic. To limit who can reach a service, check credentials in your app, or put services that must stay apart on different networks. Firewall rules for networks are on the [roadmap](/galactic-vpc/limitations-and-roadmap#planned-capabilities).
@@ -32,7 +32,7 @@ To connect from one Instance to another on the same network, use the peer's priv
- Make your app listen on IPv6 (`::`). An app that listens only on `0.0.0.0` accepts IPv4 connections only, and Instances don't have IPv4 addresses.
- In a URL, put an IPv6 address in square brackets, such as `http://[fd20:4a3b:91c2:1::1:0:2]:8080/`.
-- Private DNS names aren't available in the preview. To tell an app about its peers, pass their addresses in environment variables or a ConfigMap.
+- Connect by name rather than by address. Each Instance has a [Private DNS](/compute/private-dns) name, such as `web-default-us-central-1-0.instances.datum.internal`, that follows the Instance's address. To try it, see the [Private DNS quickstart](/domain-dns/private-dns-quickstart).
## Reach the internet