← all posts

6/28/2025 · #macos #aws

DNS Auto Updater

My DNS Auto Updater application keeps an AWS Route 53 A record pointed at your current public IP, so a subdomain always resolves to wherever your connection happens to be.

Apple App Store Image

Background

My home internet connection doesn't have a static IP, but I still wanted a reliable domain name pointing at it — for reaching devices on my home network without having to remember or look up whatever address my ISP handed out that day. I already run several domains through AWS Route 53, so rather than signing up for a third-party dynamic DNS service, I built a small menu bar app that keeps a Route 53 A record in sync with my current public IP.

DNS Auto Updater sits quietly in the macOS menu bar and checks your public IPv4 address on a schedule you control, anywhere from every 5 minutes to once a day. When the address changes, it pushes a single update to the A record for a subdomain you've chosen in Route 53. If the IP hasn't changed, it does nothing — no needless API calls, no unnecessary DNS churn.

Setup is a handful of steps: enter an AWS Access Key ID and Secret, and the app validates them and loads your Route 53 hosted zones. From there you pick an existing subdomain or create a new one directly from the app, set your update frequency, and flip the service on.

Features

A few things about how it's built that I think are worth calling out:

  • Credentials never touch disk. AWS keys are validated against Route 53 before being saved, then stored exclusively in the macOS Keychain. Everything else — the selected domain, sync frequency, last-updated timestamp — lives in a plist, but the keys themselves stay Keychain-only.
  • One dependency. The only third-party code in the app is Soto, an AWS SDK for Swift, used here for its Route 53 client. IP lookups and everything else run on native URLSession and Apple frameworks.
  • Modern Swift concurrency throughout. The whole app is built on Swift 6 with strict concurrency checking — all the AWS and network calls are async/await, with no completion-handler callbacks anywhere.
  • No privileged helper for login items. Starting the app automatically at login goes through Apple's ServiceManagement framework, so there's no separate helper process to install, update, or worry about.
  • Scoped-down IAM permissions. The app only ever needs four Route 53 actions, which makes it easy to hand it an IAM user with nothing more than it actually requires:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "route53:ListHostedZones",
        "route53:ListResourceRecordSets",
        "route53:ChangeResourceRecordSets",
        "route53:GetChange"
      ],
      "Resource": "*"
    }
  ]
}