Deploying an Application on an AWS Route 53 Domain
A step-by-step walkthrough for giving your application a public, HTTPS URL on a domain you host in AWS Route 53 — and the one add-on every target cluster needs so those URLs actually resolve.
🔑 The one requirement people miss
For a Route 53 domain, every cluster you deploy to must have the “External DNS – AWS” add-on installed. It is what writes and maintains your app’s Route 53 records. The “External DNS – PowerDNS” add-on (used for SeaGit’s managed *.segt.io domains) is not a substitute — a cluster without External DNS – AWS will deploy your app successfully but its URL will silently never resolve.
What you’ll end up with
When this is set up correctly, deploying an application produces a stable public URL on your domain, served over HTTPS with a certificate issued automatically. You never touch a DNS record or a load-balancer hostname by hand. With the example domain example.com, an app named web in an environment on that domain gets these URLs:
https://web.example.com— the main deployment, the stable name.https://web-demo-a1b2.example.com— the per-cluster name for clusterdemo-a1b2.https://k6r2kd.example.com— a preview deployment, named by a generated ID.
This guide is the AWS/Route 53-specific companion to DNS & Domains, Domains, and Applications.
How SeaGit fills in your Route 53 records
Three pieces cooperate to turn a deployment into a working URL on a Route 53 domain:
- The ingress controller — the cluster’s ingress (NGINX or Envoy Gateway API, set per cluster) sits behind a network load balancer that receives traffic for the cluster. Clusters that use an AWS Application Load Balancer use an ACM certificate instead of cert-manager.
- SeaGit’s deployer — when your app deploys, it writes the deployment’s names into your Route 53 hosted zone. For each cluster, it seeds a placeholder record that points at a hold address (
192.0.2.1) plus an owner TXT record, so the cluster’s External DNS can recognise it. - External DNS – AWS — a controller running inside your cluster. It watches the ingress and adopts the placeholder, re-pointing the record at the load balancer that is actually live, and keeps it in sync after that.
Remove that in-cluster controller and the placeholder is never adopted: the record keeps the hold address, so the URL does not resolve. Because the deployment itself still reaches started, the failure is easy to miss — which is exactly why this guide leads with it.
Prerequisites
- An AWS provider connected to the account that owns your Route 53 hosted zone.
- A domain in SeaGit backed by a Route 53 hosted zone (SeaGit can create the zone for you, or you can attach an existing one).
- One or more clusters, up and running.
See it in action
Follow the five steps below with the example domain example.com, a cluster named demo-a1b2, an environment named prod and a sample app named web. The pictures are drawings of the SeaGit console and of a browser, made to match the forms you will see. They are not screenshots, and your names will differ.
Step 1 — Connect the provider and domain
Add your AWS provider, then create a domain for example.com under it. Set Create hosted zone for this domain? to Yes to have SeaGit create the Route 53 zone, or No to attach a zone that already exists. SeaGit records the zone so it can write to it on your behalf.
The zone name you own, without a trailing dot.
Select No to attach an existing Route 53 hosted zone instead.
Step 2 — Install “External DNS – AWS” on every target cluster
On each cluster that will run the app, open its cluster add-ons and install External DNS - AWS. Set its Domains filter to the Route 53 domain(s) it should manage, including example.com.
The Route 53 zones this cluster's External DNS may write. Must include the domain your app uses.
⚠️ Do not confuse the two External DNS add-ons
- External DNS – AWS → for domains in Route 53 (this guide).
- External DNS – PowerDNS → for SeaGit’s managed
*.segt.iodomains only.
Having PowerDNS installed does not cover a Route 53 domain. Match the add-on to where your zone actually lives.
Step 3 — Create an environment on the domain and attach the cluster
Create an environment named prod, set its Domain to example.com, and keep the routing policy at Simple for now. Then open the environment’s Clusters tab and Activate demo-a1b2. Every deployment in the environment gets a subdomain on this domain.
Every deployment in this environment gets a subdomain of this domain.
| Cluster | Provider | Status | Environment |
|---|---|---|---|
| demo-a1b2 | AWS | Running | Active |
Step 4 — Deploy your application with the URL exposed
Create the application web as a Docker/Container app from the public Docker Hub registry, using the image nginx:1.27. This image serves the stock nginx welcome page, which makes it a good first test. In the Network step, answer Yes to Do you want to expose your application externally?, set the Application Port to 80 and the External Port to 443, and under DNS - Add your custom domain choose example.com as the root domain. Then press Deploy. SeaGit authors the deployment, brings up the ingress, writes the Route 53 names, and asks cert-manager for a TLS certificate in the background.
443 serves https://
DNS - Add your custom domain
Instance and deployment names are prefixed to this domain.
Step 5 — Open the URL and verify
The deployment reaches started and shows an access URL of https://web.example.com. Use Visit to open it or Copy URL to share it. Once External DNS has adopted the record, which usually takes a few minutes, the name resolves to your app.
| Deployment | Status | Access URL | Actions |
|---|---|---|---|
| main Main | started | https://web.example.com | Visit · Copy URL |
| k6r2kd Preview | started | https://k6r2kd.example.com | Visit · Copy URL |
What the sample URLs show
Open the main URL in a browser. Right after a plain nginx deploy you see the stock welcome page below. Replace the image with your own app and the same URL serves your app instead.
Welcome to nginx!
If you see this page, the nginx web server is successfully installed and working. Further configuration is required.
For online documentation and support please refer to nginx.org.
Commercial support is available at nginx.com.
Thank you for using nginx.
A preview deployment of the same app gets its own name, here k6r2kd.example.com. It runs in the same environment, so it behaves like production, but main is not changed.
Spring sale is live
This preview runs the same environment as production, on its own URL. Main stays untouched.
Main deployment: https://web.example.com
The padlock means the certificate is valid for the name in the address bar. cert-manager requests it from Let’s Encrypt through the cluster’s issuer, and the ingress serves it. The certificate lives in a Kubernetes secret, so you never upload or renew it yourself. If the cluster issues a wildcard certificate, one *.example.com certificate covers every deployment on the domain.
- Issued to
- web.example.com
- Issued by
- Let’s Encrypt (via cert-manager in the cluster)
- Status
- Valid
What gets created in Route 53
After the deployment starts and External DNS adopts the placeholder, the hosted zone for example.com holds records like these. SeaGit owns the main name. External DNS owns the per-cluster name and the TXT record that marks it as its own.
| Record name | Type | Value | Written by |
|---|---|---|---|
| web.example.com. | A (alias) | → web-demo-a1b2.example.com. | SeaGit DNS manager (routing policy, Simple) |
| web-demo-a1b2.example.com. | A (alias) | → demo-a1b2-nlb-nginx-7f3e9a1c2b.elb.us-east-1.amazonaws.com. | External DNS – AWS (adopted the placeholder) |
| web-demo-a1b2.example.com. | TXT | "heritage=external-dns,external-dns/owner=demo-a1b2.edns.aws,external-dns/resource=ingress/default/web-application-0" | External DNS – AWS (ownership marker) |
Before adoption, the per-cluster name holds the placeholder 192.0.2.1 instead of the load balancer. If you see that address, External DNS has not taken the record yet. The owner value in the TXT record is the cluster’s External DNS owner ID. It defaults to <clusterCode>.edns.aws when you leave the txt_owner_id field empty.
Multi-cluster environments
An environment can span several clusters. When it does, each deployment gets:
- a main URL —
web.example.com— that routes across the clusters, and - a per-cluster URL for each cluster —
web-<cluster>.example.com.
⚠️ Every cluster in the environment needs External DNS – AWS
Each cluster manages its own per-cluster record. If one cluster is missing the add-on, its per-cluster URL silently never resolves — while the main URL keeps working through the healthy clusters. The deployment still shows started, so the gap is invisible until someone tries the affected cluster’s URL. Install External DNS – AWS on all of them.
The request path, and how to check each hop
A visitor’s request goes through the records above, then the load balancer, the ingress and the app. Each hop has a command you can run to see where a request gets stuck.
Verify from your terminal
Run these with kubectl pointed at the cluster. The output below is example output, labelled as such.
$ dig +short web.example.com
3.231.84.19 54.158.27.203
$ curl -I https://web.example.com
HTTP/2 200 date: Sat, 10 Oct 2026 09:14:02 GMT content-type: text/html server: nginx
$ kubectl get ingress -A
NAMESPACE NAME CLASS HOSTS ADDRESS PORTS AGE default web-application-0 nginx web-demo-a1b2.example.com demo-a1b2-nlb-nginx-7f3e9a1c2b.elb.us-east-1.amazonaws.com 80, 443 12m
$ kubectl get certificate -A
NAMESPACE NAME READY SECRET AGE default wildcard-tls True wildcard-tls 2d
Route 53 alias records answer with IP addresses, so dig shows IPs rather than the load balancer name. To see the load balancer name, compare the ADDRESS column of kubectl get ingress with what the record points to in the Route 53 console.
Troubleshooting
The record has not been created yet
The app URL does not resolve and no record appears in the hosted zone. Check, in order:
- The cluster’s add-ons include External DNS – AWS with
example.comin its domains filter (having only External DNS – PowerDNS is the common trap). - The deployment shows
started. A deployment that is still building has not written its names yet. dig +short web.example.comreturns192.0.2.1. That is the placeholder, so External DNS has not adopted it yet. Check the add-on is running on the cluster.
Read the full checklist in why External DNS is not creating records.
NXDOMAIN while delegation propagates
Right after you create a hosted zone for a domain that is registered elsewhere, resolvers may not yet see the zone. Run dig NS example.com and compare the answer with the four NS records of the hosted zone. If they differ, update the nameservers at your registrar and wait for the change to propagate. No record in SeaGit or Route 53 can fix this step. See DNS & Domains and Domains.
The certificate is pending
The page loads over HTTP but the padlock is missing, or the browser shows the wrong certificate. Run kubectl get certificate -A. A certificate with READY set to False is still being issued. Use kubectl describe certificate on it for the reason. Issuance depends on DNS: cert-manager has to validate the name, so a missing or misdirected record blocks it. Fix the record first. See Certificates & TLS.
The name points at the wrong load balancer
The URL resolves, but it reaches an old or unexpected load balancer, for example after a cluster was rebuilt. Compare dig +short web-demo-a1b2.example.com with the ADDRESS column of kubectl get ingress -A. If they differ, check that External DNS – AWS is healthy on that cluster, since it is the component that re-points the record. Resolver caches can also keep the old answer for a while after a change.
My app’s URL doesn’t resolve, but the deployment says “started”
This is the most common report, and it is almost always the add-on missing from the target cluster. Install External DNS – AWS on that cluster and check the record again. More symptoms and fixes are covered on DNS & Domains and Certificates & TLS.
Frequently asked questions
Why does web.example.com return NXDOMAIN right after I set up the domain?
NXDOMAIN means the resolver found no record for that name. Right after setup this is usually one of two things: the deployment has not written its record yet, or the parent domain does not yet delegate to the Route 53 hosted zone. If you registered the domain outside Route 53, copy the zone’s four NS records to your registrar and wait for the change to propagate. Check the delegation with dig NS example.com.
Do I need External DNS – PowerDNS for a Route 53 domain?
No. A Route 53 domain needs the External DNS – AWS add-on on every target cluster. External DNS – PowerDNS is for SeaGit’s managed *.segt.io domains, and having it installed does not make a Route 53 record get created.
Why does the main URL work but one cluster’s URL does not resolve?
That cluster is missing the External DNS – AWS add-on, or its Domains filter does not include example.com. Each cluster owns its own per-cluster record (web-<clusterCode>.example.com). The main name keeps working through the healthy clusters, so only the affected cluster’s URL fails.
How do I tell whether External DNS is managing a record?
Look for the TXT ownership record on the same hostname. It contains heritage=external-dns and an owner value, which is <clusterCode>.edns.aws unless you set a custom txt_owner_id. A hostname with an A record but no owner TXT was not created by External DNS.
Does a preview deployment get its own URL and certificate?
Yes. Each deployment gets its own hostname, <dep_name>.<domain>, so a preview such as k6r2kd.example.com has its own record. Its TLS comes from the cluster’s shared wildcard certificate when the cluster issues one. Otherwise the deployment uses its own TLS secret.