SEAGIT DOCS
App on AWS Route 53

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 cluster demo-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.

Domains · New domainIllustration
Domain
example.com

The zone name you own, without a trailing dot.

Create hosted zone for this domain?
YesNo

Select No to attach an existing Route 53 hosted zone instead.

Illustration of the SeaGit console. the AWS domain for example.com, with the hosted zone choice.

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.

Cluster demo-a1b2 · Add-onsIllustration
External DNS - AWSinstalled on demo-a1b2
Domains filter
example.com

The Route 53 zones this cluster's External DNS may write. Must include the domain your app uses.

Illustration of the SeaGit console. External DNS - AWS with example.com in its domains filter.

⚠️ 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.io domains 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.

Environment prod · CreateIllustration
Environment Name
prod
Deployment Server
Seagit
Domain
example.com

Every deployment in this environment gets a subdomain of this domain.

Routing Policy
SimpleLatencyFailover
Illustration of the SeaGit console. the environment form on the example.com domain.
Environment prod · ClustersIllustration
ClusterProviderStatusEnvironment
demo-a1b2AWSRunningActive
Illustration of the SeaGit console. demo-a1b2 attached to the environment.

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.

Application web · NetworkIllustration
Do you want to expose your application externally?
YesNo
Application Port
80
External Port
443

443 serves https://

Instance Routing Policy
Simple

DNS - Add your custom domain

Root Domain Name
example.com

Instance and deployment names are prefixed to this domain.

Illustration of the SeaGit console. the app exposed on port 443 under example.com.

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.

Application web · DeploymentsIllustration
DeploymentStatusAccess URLActions
main Mainstartedhttps://web.example.comVisit · Copy URL
k6r2kd Previewstartedhttps://k6r2kd.example.comVisit · Copy URL
Illustration of the SeaGit console. the main deployment and a preview deployment, each with its access 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.

Visitor · main URLIllustration
https://web.example.com

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.

Illustration of a browser (not a live capture). https://web.example.com serving the nginx default page.

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.

Visitor · preview URLIllustration
https://k6r2kd.example.com
Acme StorePreview · k6r2kd

Spring sale is live

This preview runs the same environment as production, on its own URL. Main stays untouched.

Shop nowCompare with main

Main deployment: https://web.example.com

Illustration of a browser (not a live capture). https://k6r2kd.example.com, a sample landing page served by a preview deployment.

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.

Visitor · certificateIllustration
https://web.example.com
Issued to
web.example.com
Issued by
Let’s Encrypt (via cert-manager in the cluster)
Status
Valid
Illustration of a browser (not a live capture). the certificate details behind the padlock on https://web.example.com.

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.

Hosted zone example.com, after the web deployment is live
Record nameTypeValueWritten 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)
Illustrative example, not a live zone. Names, IPs and load balancer hostnames are placeholders in the real naming format.

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.

Request flow from browser to pod on a Route 53 domainA browser asks Route 53 for web.example.com. Route 53 answers with the per-cluster name, which is an alias to the cluster network load balancer. The load balancer sends traffic to the ingress controller, which forwards it to the Service and then to the app pods. External DNS in the cluster writes and keeps the per-cluster record in Route 53.Browserhttps://web.example.comRoute 53hosted zone example.comNLBdemo-a1b2-nlb-nginxIngressNGINX or Envoy GatewayServicethen app podsDNSaliasTCP/TLSHTTPExternal DNS – AWSruns in the clusterwrites and keeps the per-cluster record currentweb.example.com aliases web-demo-a1b2.example.com, which aliases the NLB.
Request path, simplified. The ingress controller is whichever one the cluster runs (NGINX or Envoy Gateway API). TLS terminates at the ingress using the certificate cert-manager issued.

Verify from your terminal

Run these with kubectl pointed at the cluster. The output below is example output, labelled as such.

Check the name, the TLS and the ingressExample output

$ 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
Commands are real. Output is illustrative: your names, IPs and timings will differ.

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.com in 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.com returns 192.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.