Article

Bringing Existing Route 53 Records Under Infrastructure as Code

Bringing Existing Route 53 Records Under Infrastructure as Code

Your first DNS deployment should be boring. Nobody should notice it happened. The website keeps loading, email keeps arriving, and certificate renewals keep working. That is the goal when you bring existing Route 53 records under infrastructure as code: give live infrastructure a reviewed, repeatable definition without accidentally changing its behavior.

The tricky part is rarely writing a record declaration. It is proving that your configuration describes everything you intend to manage, with the same routing behavior and ownership boundaries. A safe adoption follows a clear path: inventory the hosted zone, export its configuration, review the differences, establish resource ownership, and run a deployment that proposes no DNS changes.

A successful first deployment preserves the DNS behavior already serving production.

  • Build your inventory from the Route 53 API, including routing attributes and private records.
  • Keep the initial configuration faithful to production before attempting cleanup.
  • Require a reviewed plan with no unexpected creates, updates, replacements, or deletions.

Set the Ownership Boundary Before Exporting Records

Start with one hosted zone. Confirm its AWS account, hosted zone ID, zone name, and whether it is public or private. Names alone are insufficient. Two hosted zones can share a name, and public and private zones can intentionally serve different answers.

Then identify who writes records today. A platform team may manage application records manually while a Kubernetes controller manages service endpoints. Another stack may own certificate validation records. Exporting those records does not transfer responsibility for them.

Write down which records your new configuration will own. Name the existing owner for everything outside that boundary. If you intend to transfer records from another system, coordinate that handoff before enabling writes from your new pipeline. Two active reconcilers can repeatedly undo each other's changes.

Arrange a short pause in DNS edits during capture and review. If a pause is impractical, record the capture time and repeat the inventory before deployment. You need a baseline that still represents production when you act on it.

Capture a Complete Hosted-Zone Inventory

Use credentials with read access first. Verify the account, inspect the zone, and retrieve its record sets. The following commands use an illustrative hosted zone ID. Replace it with the verified ID before running them.

aws sts get-caller-identity

aws route53 get-hosted-zone \
  --id Z0123456789EXAMPLE \
  --output json > hosted-zone.json

aws route53 list-resource-record-sets \
  --hosted-zone-id Z0123456789EXAMPLE \
  --output json > record-sets.json

Keep these files as the raw capture. Add a separate capture note with the account ID, hosted zone ID, timestamp, CLI version, and command arguments. Protect the files according to your repository policy. DNS data can expose internal names, private addresses, and details about your infrastructure.

Pagination deserves explicit attention. The AWS CLI handles pagination for this operation by default. Avoid options that disable pagination or cap the total returned items unless you also implement continuation handling. AWS documents this behavior in its record listing reference and advises against changing records while paging through a zone.

For a direct API integration, follow the service's continuation fields until the listing is complete. A successful request does not prove you captured the entire zone.

Review each record set as a structured object. Its identity and behavior can extend beyond its name and type. Preserve the fields that apply:

  • Record name, record type, TTL, and every resource record value.
  • Alias target DNS name, target hosted zone ID, and target health evaluation setting.
  • Set identifier and routing attributes for weighted, latency, failover, geolocation, or other configured policies.
  • Health check associations and multivalue answer settings.
  • Delegation records, certificate validation records, wildcard names, and TXT value boundaries.

Capture zone context separately. Private zone VPC associations, tags, and linked health checks may sit outside a record exporter's scope. Define whether this adoption covers records only or those related resources too. Keep the existing hosted zone itself intact during a records-only migration.

Export Configuration Without Rewriting Production

Choose a tool that can represent the record features actually present in your inventory. A declarative DSL exporter can provide a useful starting point. Terraform requires resource declarations plus state adoption. CloudFormation adoption depends on support for the resource type and import workflow you intend to use.

Do not assume that an exporter understands every routing policy because it handles basic A and CNAME records. Check its output against representative records from your zone. If a field disappears, stop and determine whether the tool can preserve that behavior.

For the first commit, aim for fidelity. Keep current TTLs. Preserve TXT values and their string boundaries. Retain routing identifiers. Avoid renaming resource addresses, reorganizing records into reusable modules, or correcting old conventions during the initial adoption.

An ordinary Terraform record declaration might look like this:

resource "aws_route53_record" "application" {
  zone_id = "Z0123456789EXAMPLE"
  name    = "app.example.com"
  type    = "A"
  ttl     = 300
  records = ["192.0.2.25"]
}

This declaration alone does not establish that Terraform owns the existing record. You must also import the live resource into the correct state address. Confirm the import identifier format for your installed provider version, particularly for records with routing set identifiers.

A DSL tool that compares its configuration directly with AWS may not use a separate state file. In that case, inspect how it handles records absent from the configuration. A partial export combined with automatic deletion can turn a small adoption into a zone-wide outage.

Cross-Check Public Answers Against the Export

Once the exported configuration looks complete, check what users can resolve. A DNS record lookup helps cross-check publicly resolvable records against the export. Start with known public names for the website, mail service, and other critical endpoints.

Treat this as a behavior check with a different vantage point. A lookup can reveal an unexpected address or a missing public answer. It cannot replace the hosted-zone inventory. Public queries do not enumerate every name, expose private hosted zones, or reveal all routing configuration.

Alias records make that distinction clear. A public query may return addresses for an AWS endpoint, while the exported configuration contains an alias target and health evaluation setting. Compare the observed answer with the intended target behavior. Do not replace an alias declaration with whichever addresses appeared during one lookup.

Routing policies also affect the comparison. Weighted or latency-based records may return different answers across queries and locations. A single response cannot establish that every configured branch exists or works.

For private names, query from an appropriate network context. For public discrepancies, account for caching and check authoritative answers where needed. An answer from a recursive resolver may reflect an earlier value until its cached TTL expires.

Separate Export Differences From Actual Drift

There are three artifacts to reconcile: the captured AWS inventory, the generated configuration, and the live records at review time. A difference between any two needs an explanation.

Some differences concern representation. A tool may normalize trailing dots, change letter case, or reorder values. Others alter behavior: a missing mail server, a changed TTL, a different failover role, or disabled target health evaluation.

Use a semantic comparison where possible. Match records by hosted zone, name, type, and routing identifier where applicable. Compare alias properties and routing fields explicitly. Sorting JSON object keys can make a text diff easier to read, but it does not establish functional equivalence.

Watch TXT records closely. Quoting and string splitting can be confusing in exports and generated code. Confirm that the tool preserves the intended record values rather than merely producing similar-looking text.

If the live zone changed after capture, investigate the change before updating your baseline. Accept legitimate operational work into the configuration. Resolve unauthorized or accidental changes through your normal review process. Avoid silently overwriting either version.

Establish Management Without Changing DNS

For a state-based workflow, import existing resources into the intended state backend. Protect that backend, enable locking where supported, and retain a recoverable state snapshot. Verify that the workspace, AWS account, and resource addresses match the reviewed configuration.

Importing a resource and changing its remote configuration are different operations. Review any workflow that combines them carefully. An import plan can still contain updates caused by mismatched configuration.

For a direct reconciliation tool, run its preview mode against the verified zone. Confirm that excluded records remain outside its management boundary. Pay particular attention to default behavior around zone apex NS and SOA records, which already exist in a hosted zone.

Do not use overwrite settings or broad ignore rules merely to silence differences. Understand each proposed action. A clean-looking plan is useful only when it reflects accurate configuration and explicit ownership.

Make the First Deployment a No-Change Checkpoint

Refresh the inventory shortly before the first deployment. Run the plan or preview using the same versions, credentials, and configuration that the deployment job will use. Read the full output, including warnings and resources outside the immediate record set.

The acceptance criteria should be concrete:

  • No DNS record creates, updates, replacements, or deletions are proposed within the adoption scope.
  • Imported resources map to the correct existing record sets.
  • Excluded records remain untouched and have documented owners.
  • Critical public and private names still resolve as expected from appropriate locations.
  • The reviewed commit, configuration, and deployment target match the approved plan.

If the tool proposes replacing or recreating an existing record, stop. Investigate identity matching, import details, and unsupported fields. Do not accept a DNS write simply because the resulting value appears unchanged.

For Terraform, a separate import followed by a normal plan should leave no infrastructure changes to apply. With configuration-driven imports, the reviewed operation may add state associations while leaving remote records unchanged. Keep that distinction visible in the deployment record.

After the operation, repeat the plan and the critical resolution checks. Keep the raw capture, reviewed configuration, import mapping, and execution output available. If something unexpected occurs, inspect the actual live records before choosing a recovery action. Restoring an old state file alone does not restore DNS.

Keep Future DNS Changes Reviewable

Commit the accepted baseline and make the repository the normal path for changes within its ownership boundary. Pin tool versions and preserve state securely where applicable. Run read-only drift checks on a schedule so emergency console edits become visible.

Retain an emergency change process. During an incident, an engineer may need to update DNS directly. Record that action and reconcile the repository afterward. The next routine deployment should not undo the fix.

Make cleanup a later change with its own review. Shortening TTLs, removing stale records, and consolidating routing policies may all be worthwhile. Each becomes easier to assess once the existing behavior has a trusted definition.

Give Every Live Record an Accountable Home

The adoption is complete when you can explain what the zone contains, which system owns each managed record, and why the configuration matches production. The first deployment should confirm that understanding without changing the answers your users receive.

From that baseline, infrastructure as code earns its place in daily operations. A DNS change becomes a small, reviewable diff with a known target and a checked plan. Production keeps working while the team gains a reliable way to manage what comes next.

Go deeper

Guides on the same workflow

What Is Codenization? Managing Services as Code

8 min read

Codenization turns live service configuration, like DNS, IAM, schemas, and monitors, into declarative Ruby DSL files you can grep, review, and re-apply. Learn where the approach came from, how the export-review-apply loop works, and when to choose it.

Read guide

Export, Review, Apply: The Codenize Workflow

8 min read

The practical getting-started guide: export live configuration with one command, commit it to Git, preview every change with a dry-run, and apply idempotently. Includes repo layout, review checklists, CI wiring, credentials, and rollback.

Read guide

Idempotency in Infrastructure as Code, Explained

7 min read

Idempotency means the same code applied N times yields the same result. See how codenize tools achieve it by diffing a declarative DSL against live APIs, with Route 53 and MySQL examples and the pitfalls to watch for.

Read guide