03 · Cloud DNS & Domains¶
Every service built so far has been reached by IP address or an auto-generated
*.run.app / *.a.run.app hostname. Cloud DNS is Google's managed,
authoritative DNS service — it lets you host the DNS records for your own
domain on Google's global anycast name-server network, so app.example.com
resolves to your load balancer instead of a raw IP nobody can remember.
Managed zones: public vs. private¶
A managed zone is a container for the DNS records of one domain (and its subdomains). Cloud DNS supports two kinds:
| Public zone | Private zone | |
|---|---|---|
| Visible to | The whole internet | Only VPCs you attach it to |
| Typical use | example.com for a public website |
internal.example.com for services only reachable inside your VPC |
| Requires domain ownership | Yes — you must own/control the domain | No — any name works, it's never delegated publicly |
This module focuses on a public zone, since that's what fronts a live web app.
Create a managed zone¶
gcloud services enable dns.googleapis.com
gcloud dns managed-zones create example-com \
--dns-name="example.com." \
--description="Public zone for example.com"
gcloud dns managed-zones describe example-com --format="value(nameServers)"
# ns-cloud-a1.googledomains.com.
# ns-cloud-a2.googledomains.com.
# ns-cloud-a3.googledomains.com.
# ns-cloud-a4.googledomains.com.
Cloud DNS assigns four name servers per zone. Nothing resolves publicly until
your domain registrar's NS records for example.com point at these four —
that delegation step happens outside GCP, at whichever registrar sold you the
domain (Google Domains, Namecheap, etc.).
Add records¶
Records are added and removed through a transaction: you start one, stage adds/removes, then execute it as a single atomic change.
gcloud dns record-sets transaction start --zone=example-com
gcloud dns record-sets transaction add 34.117.20.10 \
--name="example.com." \
--ttl=300 \
--type=A \
--zone=example-com
gcloud dns record-sets transaction add "www.example.com." \
--name="www.example.com." \
--ttl=300 \
--type=CNAME \
--zone=example-com
gcloud dns record-sets transaction execute --zone=example-com
gcloud dns record-sets list --zone=example-com
# NAME TYPE TTL DATA
# example.com. NS 21600 ns-cloud-a1.googledomains.com.,...
# example.com. SOA 21600 ns-cloud-a1.googledomains.com. ...
# example.com. A 300 34.117.20.10
# www.example.com. CNAME 300 example.com.
A trailing dot (example.com.) means "this is a fully-qualified name, stop
here" — DNS tooling is strict about it, and a missing trailing dot on a
--name value is one of the most common Cloud DNS mistakes.
Verifying propagation¶
dig example.com A +short
# 34.117.20.10
dig www.example.com CNAME +short
# example.com.
dig example.com NS +short
# ns-cloud-a1.googledomains.com.
# ns-cloud-a2.googledomains.com.
If dig against Google's own name servers works but the domain doesn't
resolve from your home network, the registrar-side NS delegation likely
hasn't propagated yet — that step is outside Cloud DNS's control and can take
anywhere from minutes to 48 hours depending on the registrar and old TTLs.
Pointing a domain at a load balancer¶
For the global HTTP(S) load balancer built in Module 02, point the A record at the reserved global IP instead of a VM IP:
gcloud compute addresses describe web-lb-ip --global --format="value(address)"
# 34.117.20.10
gcloud dns record-sets transaction start --zone=example-com
gcloud dns record-sets transaction add 34.117.20.10 \
--name="example.com." --ttl=300 --type=A --zone=example-com
gcloud dns record-sets transaction execute --zone=example-com
For Cloud Run, use a CNAME to the domain-mapping hostname Google gives you
after running gcloud run domain-mappings create, rather than a hardcoded IP
— Cloud Run's edge IPs are not guaranteed stable.
DNSSEC¶
DNSSEC adds cryptographic signatures to DNS responses, preventing cache poisoning / spoofed answers. Cloud DNS supports it per zone:
gcloud dns managed-zones update example-com --dnssec-state=on
gcloud dns dns-keys list --zone=example-com
Enabling DNSSEC produces a DS record that must also be published at the registrar — enabling it in Cloud DNS alone does nothing until the registrar side is configured too, and the two must be kept in sync if you ever rotate keys.
Terraform equivalent¶
resource "google_dns_managed_zone" "example_com" {
name = "example-com"
dns_name = "example.com."
}
resource "google_dns_record_set" "root_a" {
name = google_dns_managed_zone.example_com.dns_name
managed_zone = google_dns_managed_zone.example_com.name
type = "A"
ttl = 300
rrdatas = ["34.117.20.10"]
}
resource "google_dns_record_set" "www_cname" {
name = "www.${google_dns_managed_zone.example_com.dns_name}"
managed_zone = google_dns_managed_zone.example_com.name
type = "CNAME"
ttl = 300
rrdatas = [google_dns_managed_zone.example_com.dns_name]
}
Gotchas¶
- Delegation lives at the registrar, not in GCP. Creating a Cloud DNS zone and its records does nothing publicly until the domain's registrar points its NS records at Cloud DNS's four name servers — a very common "why doesn't my domain work" dead end.
- Trailing dots are load-bearing.
--nameand--dns-namevalues are fully-qualified domain names and must end in.; forgetting it silently creates a record under the wrong (relative) name. - TTL controls how long old answers linger. Lowering a record's TTL before a planned change (e.g. a migration cutover) so cached answers expire faster is a standard trick — raising TTL back afterward reduces query volume/cost.
- DNSSEC needs registrar-side DS records too. Turning on
--dnssec-state=onin Cloud DNS is only half the job. - Private zones need explicit VPC attachment (
--networks=) — a private zone created without one resolves nowhere.
Cleanup¶
gcloud dns record-sets transaction start --zone=example-com
gcloud dns record-sets transaction remove 34.117.20.10 \
--name="example.com." --ttl=300 --type=A --zone=example-com
gcloud dns record-sets transaction remove "example.com." \
--name="www.example.com." --ttl=300 --type=CNAME --zone=example-com
gcloud dns record-sets transaction execute --zone=example-com
gcloud dns managed-zones delete example-com
Cheat sheet¶
| Command | Purpose |
|---|---|
gcloud dns managed-zones create --dns-name= |
Create a public/private zone for a domain. |
gcloud dns managed-zones describe --format="value(nameServers)" |
Get the four name servers to configure at the registrar. |
gcloud dns record-sets transaction start/add/remove/execute |
Atomically change records in a zone. |
gcloud dns record-sets list |
List all records in a zone. |
gcloud dns managed-zones update --dnssec-state=on |
Enable DNSSEC signing. |
dig <name> <type> +short |
Query DNS directly to verify what's published. |
How It Actually Works¶
Cloud DNS answers queries from a globally distributed anycast network of name servers, and every record change you publish becomes visible essentially everywhere simultaneously because Google's DNS infrastructure propagates zone updates internally rather than relying on the classic internet DNS assumption of independent resolvers caching stale data — the delay users experience after a change is really the TTL of the old record already cached in resolvers upstream of Google (their ISP's resolver, browser cache), not a delay in Cloud DNS itself. This is why lowering a record's TTL before a planned migration is the standard trick: it shrinks the maximum staleness window everyone else's caches will hold once you actually flip the record. DNSSEC works by chaining cryptographic signatures from the root zone down through each delegation to your zone's records, so a validating resolver can prove a response wasn't forged in transit — enabling it on a Cloud DNS zone means Google signs your zone's records and publishes a DS record your registrar must also publish, forming the trust chain's next link.
Exercise¶
Create a managed zone for a domain you control (or a throwaway one for
practice), add an A record and a www CNAME, and verify both with dig
against Google's own name servers using dig @ns-cloud-a1.googledomains.com
example.com A. Then lower the A record's TTL to 60, change the IP, and
time how long it takes dig to return the new value. Clean up the zone
afterward.