Why moving from Google Domains to Google Cloud DNS was painful—and how we fixed it

By
Yonatan Zunger
Team Humu

This blog post is the first in an intermittent series about things we’ve learned in our first year as a startup. The goal of these posts is to help others avoid the same misery—and to make work better. ← see what we did there?

We recently migrated Humu’s domain name services from Google Domains to Google Cloud DNS, because in our use case it’s more powerful, more flexible, and gives us service that wasn’t going to cut it with Google Domains. Why we did this matters less than what happened when we did—and what we discovered in the process.

The good news is that Google Cloud DNS provides excellent instructions about how to migrate name service into it. The bad news is that Google Domains is missing a critical feature that lets you migrate things out of it easily. What you need to do not only isn’t documented, but it’s super easy to mess up.

The right way is to follow the Cloud DNS migration guide exactly—but the catch is that Google Domains doesn’t support exporting zone data! In this post we’ll lay out documentation (with some slightly janky copy-pasting), as it worked for the Humu team.

Step 1: Create a Cloud DNS Zone.

This is just the first step of the Cloud DNS migration instructions. Pick a GCP project you’re going to use for DNS (or create a new one), then rungcloud dns managed-zones create --dns-name="example.com." --description="A zone" "examplezonename"where you replace “example.com” with your domain name (but keep that final trailing dot!), “examplezonename” with the name you want to give your zone, and the description with a description that’s meaningful to you.

Step 2: Copy a bunch of stuff into a spreadsheet.

You’re going to have to convert a bunch of HTML tables from the Domains admin panel into a precise text format. The easiest way to do this involves a spreadsheet. So open a new spreadsheet of your favorite sort, go to Google Domains, go to the domain whose DNS you want to move, and switch to the “Configure DNS” tab.Start with the “Custom Resource Records” section at the bottom. There’s a table whose columns are name, type, TTL, and data: copy-paste it into your sheet. Then go to the “Synthetic Records” section above it, and for each section open the expando until you see a similarly-shaped table; copy those rows into the sheet as well.

Step 3: Reformat everything correctly.

Unfortunately, this table doesn’t look like a properly-formatted zone file. The table you get on the web page has the formNameTypeTTLDataDataDataAnd the TTL is given as a string like “1h” or “2w”. In a zone file, things have to be formatted asName(TTL)TypeData(TTL)TypeData(TTL)TypeDatawhere now the TTL is given as an integer number of seconds (and is skipped if it’s the same as the value set on the previous line), and the type has to be repeated on each line!So you need to go through this file and manually reformat it this way.

3A: Split up lines

If you have a row where the “data” column has multiple lines, split that into N rows, with one line of data on each row. Copy the “name”, “type” and “ttl” columns so they’re the same on each row.

3B: Reorder columns and sort the sheet

Move the TTL column to the left of the Type column. Then sort the entire sheet by name, subsorted by type, subsorted by TTL.

3C: Fix the TTL’s

Almost all of the TTL’s you see are probably going to be identical. Write down that common TTL value somewhere, then delete all cells whose TTL is equal to that. For the remaining cells, replace the TTL value with a corresponding number of seconds – e.g., change “1h” to 3600, “2w” to 1209600, and so on.

3D: Turn this into a text file.

Export this spreadsheet into a tab-separated values file. (If you want to be traditional, name it “dns.bind”) Then open that in your favorite text editor. (And remember to use a text editor, not Word or the like!)

3E: Turn this into a proper zone file.

At the top of the file, add the lines$ORIGIN example.com.$TTL 3600where you replace “example.com” with your own domain name (but leave that final dot in place! It’s important), and 3600 with whatever that common TTL value was you had, again in seconds.

Those of you who know DNS well will realize this isn’t quite a proper zone file: it lacks SOA and NS lines at the top. That’s OK: the whole point of Cloud DNS is to override both of those, so it doesn’t require them in zone files it’s importing.

Step 4: Import the zone file

Now we resume the ordinary migration instructions. Rungcloud dns record-sets import --project <project-name> --zone <zone-name> --zone-file-format <filename>where project-name is the name of the GCP project you’re using for Cloud DNS, zone-name is the zone you created in step 1, and filename is the name of the zone file you just created.

Step 5: Make sure it reached what’s going to be your new name server

You can get the list of name servers you were assigned by runninggcloud dns managed-zones describe <zone-name> --project <project-name>

This output will end with a list of name servers, all looking something like “ns-cloud-a1.googledomains.com”. You can see if these changes have propagated to the authoritative name server by running dig hostname @nameserverwhere “nameserver” is any of those returned nameservers (without a final dot), and “hostname” is any host you’re interested in. I strongly recommend checking not only the hosts you explicitly configured, but any of the “synthetic record” hosts: for example, if you use GSuite on your domain, check mail.mydomain.com, sheets.mydomain.com, and so on.

If the information has reached the authoritative name server, you should see lines in the output that look like;; ANSWER SECTION:mail.humu.com.        3600 IN CNAME ghs.googlehosted.com.

A lack of an “answer section” means that things haven’t propagated yet; wait a few minutes and try again.

If things don’t work after a while, or if you got an error trying to import your records, you’re going to have to massage your file and try re-uploading it, or editing things by hand in the Cloud DNS UI.

Warning #1: At this time, and at this time only, it’s wise to use the --delete-all-existing option to the import command. This will overwrite your domain’s entire configuration with what’s in the file. If you do this after you’ve switched to using the new domain servers, really Exciting Things may happen. (It’s sometimes wise to do that anyway, but if you’re doing that, then you should understand DNS well enough to know the risks and consequences.)

Warning #2: Without that flag, the import command will fail if there are any records already uploaded for a particular name and type. It isn’t smart enough to replace just some of the records; it can only either append entirely new records or stomp everything. This means that if there are errors in just a few things, or any errors after you go live, you’re stuck using the UI to edit things one at a time. If, like me, you forgot to stick a $TTL line in the top of a second update you were adding after going live, then you’ll suddenly see a bunch of rows with a TTL of zero, which won’t work at all, and will get the special fun of editing them by hand while swearing a lot.

Step 6: Switch over DNS.

Going back to your Google Domains page, at the very top there’s a radio button to switch between the Google Domains name servers and your own custom name servers. You’ll note that the Google Domains ones are almost identically named to your new ones: they’re all “ns-cloud-d1.googledomains.com”. This isn’t a coincidence; it’s the same infrastructure under the hood. Simply select “custom domains,” add the list of name servers you got in step 5, and hit Save.Now you can test that this is working withdig +short NS example.comwhere example.com is again replaced with your domain. Once this starts showing the new name servers, your change should be propagated and Cloud DNS should be managing your domain. You can verify this by runningdig hostnameon various hosts, just like you did in step 5 but now without the explicitly-specified nameserver. You should now see an answer section which fully resolves, like;; ANSWER SECTION:mail.humu.com.        3600 IN CNAME ghs.googlehosted.com.ghs.googlehosted.com.   61 IN A     172.217.164.115

This is your sign that everything is working.

Conclusion: This is way harder than it needs to be.

Also, way too error-prone. Google Domains really needs to add an “export zone file” button to its UI to do this automatically.