Skip to content

Provision Hosted DNS infrastructure: apex domain, AWS, Route 53, Railway, PlanetScale #984

Closed

Description

@nick-potts

Question

Parent map: #973. Decided in Design the Hosted DNS service. Before the service can be built or the Cloud side tested: buy a dedicated apex for generated names (not ployz.dev); create the AWS account; create one Route 53 hosted zone for it and file the record-quota increase; provision an EC2 instance for the API; create the PlanetScale Postgres database; create a Google Cloud project with the Public CA API enabled and an EAB key for Hosted DNS's one Google Trust Services ACME account (wildcard decision). HITL: hand the human a checklist (/wizard candidate) for the parts only they can do. Resolves with the facts later tickets depend on: apex name, zone id, instance address, Google Cloud project id, where credentials live.

Activity

  1. self-assigned this
    on Sep 24, 2026
  2. nick-potts commented on Sep 24, 2026

    @nick-potts
    ContributorAuthor

    Provisioning checklist

    Settled in session (2026-09-24):

    • Apex: ployz.app (already owned, currently on Cloudflare NS). Generated names are <name>.ployz.app.
    • AWS account: existing 557003269662. No new account.
    • Region: PlanetScale AWS eu-central-1 (Frankfurt, slug eu-central) and Railway EU West (Amsterdam), about 8ms apart. We avoided AWS us-east-1 because of its outage history. Route 53 is global; its change API still depends on us-east-1.
    • API host: Railway, not EC2. The API is stateless HTTP; EC2's only advantage was role-based credentials. This overrides "API runs on EC2" in Design the Hosted DNS service.

    1. Apex → Route 53

    • Check ployz.app's current Cloudflare zone records. Move anything still in use, or confirm nothing is.
    • Route 53 → create a public hosted zone ployz.app. Record the zone id.
    • At the registrar, set nameservers to the zone's 4 awsdns NS. Turn off Cloudflare DNSSEC first, if it's on.
    • Check it: dig +short NS ployz.app returns awsdns.
    • Raise the record quota Deferred. The default is 10,000 records per zone. At 3 per cluster that's about 3,300 clusters, and raising it adds a charge per record. When it's needed: Service Quotas, with the console region set to us-east-1 (Route 53 quotas only appear there).

    2. API access and Railway

    • IAM user ployz-hosted-dns with no console access. Allow only route53:ChangeResourceRecordSets, route53:ListResourceRecordSets on arn:aws:route53:::hostedzone/<zone-id>, plus route53:GetChange. Create one access key.
    • Railway: new project ployz-hosted-dns, separate from Cloud because this is shared infrastructure. Empty service api, region EU West (Amsterdam). The code comes later.
    • Put the access key and the PlanetScale URL into the service's variables.
    • Add the custom domain dns.ployz.app to the service, and create the CNAME that Railway gives you in Route 53.

    3. PlanetScale Postgres

    • Org planetscale-nickpotts → new Postgres database ployz-hosted-dns · region eu-central-1 (Frankfurt) · smallest size.
    • Create an app role/password and record the connection string.

    4. Reply here with

    • Zone id and the 4 NS
    • Railway project and service ids
    • PlanetScale host and database name
    • Where the credentials live (password manager entry, etc.). Never paste secrets here.
  3. changed the title [-]Provision Hosted DNS infrastructure: apex domain, AWS, Route 53, EC2, PlanetScale[/-] [+]Provision Hosted DNS infrastructure: apex domain, AWS, Route 53, Railway, PlanetScale[/+] on Sep 24, 2026
  4. nick-potts commented on Sep 24, 2026

    @nick-potts
    ContributorAuthor

    Decisions recorded while provisioning

    These go into the resolution and into the Hosted DNS build spec.

    • API host: Railway, not EC2. The API is stateless HTTP; EC2 only offered role-based credentials. It uses an IAM user with a single access key limited to the ployz.app zone. This overrides "API runs on EC2" in Design the Hosted DNS service.
    • Regions: PlanetScale AWS eu-central-1 (Frankfurt) and Railway EU West (Amsterdam).
    • Apex: ployz.app, with the registration transferred from Cloudflare to Route 53. Cloudflare Registrar requires Cloudflare's nameservers.
    • No Route 53 record-quota raise. This overrides "raised via support up front". The default of 10,000 record sets per zone fits about 3,300 names at 3 each (apex A, apex AAAA, wildcard CNAME). Machine IPs share one record set, so the cost per name doesn't grow with the cluster.
    • Capacity guard at mint: POST /domains returns 503 capacity_exhausted when (live_names + 1) * 3 + fixed > 10_000 - headroom, with headroom around 500. Minting is the only thing that adds record sets, and existing names are never affected. Cloud's sync already treats failures as non-blocking.
    • Alerting is ours, not AWS's. Route 53 publishes no record-count metric. A daily job calls GetHostedZoneLimit (real count against the limit), alerts at 80%, and flags drift from the database count. It alerts through a log line or webhook.
    • When it fills: request the increase in Service Quotas, with the console region set to us-east-1, or add a second apex. The API already returns the granted name, so Cloud doesn't change.
  5. nick-potts commented on Sep 25, 2026

    @nick-potts
    ContributorAuthor

    Provisioned (2026-09-25)

    Route 53

    • Hosted zone ployz.app, id Z01536093OYT3FLZSI80R, in account 557003269662.
    • NS: ns-656.awsdns-18.net, ns-1771.awsdns-29.co.uk, ns-133.awsdns-16.com, ns-1029.awsdns-00.org
    • Registrar transfer from Cloudflare: IN_PROGRESS (submitted 2026-09-24). Cloudflare had no live records and no DNSSEC, so there was nothing to migrate or turn off.
    • Records: dns.ployz.app CNAME ar34zafx.up.railway.app, plus Railway's _railway-verify.dns TXT. Route 53 already serves both. They go live when the delegation moves.

    IAM

    • User ployz-hosted-dns, no console access. Its inline policy route53-ployz-app allows ChangeResourceRecordSets, ListResourceRecordSets, GetHostedZone and GetHostedZoneLimit on the zone only, plus GetChange. Tested: the zone limit reads 2/10,000, and ListHostedZones is denied.
    • One access key. It exists only in Railway's variables.

    Railway

    • Project ployz-hosted-dns (898f2c4a-5bbe-44fa-b3dd-10836cadbd66), in the Order Editing workspace next to Ployz Dashboard, environment production.
    • Service api (f8f9bc48-8a7e-46ac-ba4e-6fda87771a16), region EU West (Amsterdam). Empty, no source yet.
    • Variables: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION=us-east-1, ROUTE53_HOSTED_ZONE_ID, HOSTED_DNS_APEX=ployz.app.
    • Custom domain dns.ployz.app → port 8080. Its certificate waits on the delegation.

    Remaining

    • Once the transfer completes, set the registered domain's nameservers to the 4 above (the agent can do this with the ployz admin profile).
    • PlanetScale Postgres ployz-hosted-dns, eu-central-1: create it and an app role. Store the URL as DATABASE_URL on Railway api.
    • After that, deactivate the nick-admin bootstrap access key.
  6. nick-potts commented on Sep 25, 2026

    @nick-potts
    ContributorAuthor

    Provisioned, part 2 (2026-09-25)

    Domain

    • The transfer to Route 53 finished. Route 53 is now the registrar, the domain expires 2028-03-26 with auto-renew, and the transfer lock is on.
    • The registry delegates ployz.app to the 4 awsdns NS, and Cloudflare's resolver already returns dns.ployz.app → ar34zafx.up.railway.app. Railway's domain verification and certificate are waiting for its next check.

    PlanetScale

    • Org ployz, Postgres database ployz-hosted-dns, region eu-central (AWS eu-central-1), branch main, PG 18.6.
    • Role api (pscale_api_nu9peadyfinx…), which inherits postgres so the service can run its own migrations. It never expires.
    • Host aws-eu-central-1-2.pg.psdb.cloud:5432, database postgres, sslmode=verify-full. The URL is stored only in Railway api as DATABASE_URL. Tested: connect, then CREATE/DROP TABLE.
    • Note for the build: libpq needs sslrootcert=system (or a CA file). Rust clients using rustls with system roots don't.

    Map updates

    Remaining (the spec added a certificate endpoint after this ticket was written)

    • Google Cloud project for Google Trust Services: enable publicca.googleapis.com, create an EAB key, and store GTS_EAB_KID and GTS_EAB_HMAC in Railway api.
    • Deactivate the nick-admin bootstrap key.
  7. nick-potts commented on Sep 25, 2026

    @nick-potts
    ContributorAuthor

    Resolution

    Hosted DNS infrastructure is provisioned. Details: part 1, part 2, and the decisions.

    Piece Fact
    Apex ployz.app. Registrar Route 53 (moved from Cloudflare), expires 2028-03-26, auto-renew and transfer lock on
    Zone Route 53 Z01536093OYT3FLZSI80R, account 557003269662, 4 awsdns NS delegated
    API Railway project ployz-hosted-dns (898f2c4a…), service api (f8f9bc48…), EU West, https://dns.ployz.app → port 8080, certificate valid
    AWS creds IAM user ployz-hosted-dns, limited to the zone. Its key exists only in Railway variables
    Database PlanetScale org ployz, Postgres ployz-hosted-dns, eu-central-1, role api. The URL exists only in Railway DATABASE_URL
    CA GCP project ployz-acme, Public CA API enabled. EAB key creation moved to Deploy Hosted DNS to production (unused keys expire after 7 days)

    Overrides from Design the Hosted DNS service:

    • Railway replaces EC2. The spec and the deploy ticket are updated.
    • No up-front record-quota raise. Instead: a capacity guard at mint and a daily GetHostedZoneLimit alert at 80%.

    Credentials live in Railway variables (service api). The admin bootstrap is the local AWS profile ployz (IAM user nick-admin); deactivate its key once nothing needs admin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

wayfinder:taskWork required to unblock a decision

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions