You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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 quotaDeferred. 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.
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.
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.
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.
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.
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.
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.
Activity
nick-potts commentedon Sep 24, 2026
Provisioning checklist
Settled in session (2026-09-24):
ployz.app(already owned, currently on Cloudflare NS). Generated names are<name>.ployz.app.557003269662. No new account.eu-central-1(Frankfurt, slugeu-central) and Railway EU West (Amsterdam), about 8ms apart. We avoided AWSus-east-1because of its outage history. Route 53 is global; its change API still depends onus-east-1.1. Apex → Route 53
ployz.app's current Cloudflare zone records. Move anything still in use, or confirm nothing is.ployz.app. Record the zone id.awsdnsNS. Turn off Cloudflare DNSSEC first, if it's on.dig +short NS ployz.appreturns awsdns.Raise the record quotaDeferred. 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
ployz-hosted-dnswith no console access. Allow onlyroute53:ChangeResourceRecordSets,route53:ListResourceRecordSetsonarn:aws:route53:::hostedzone/<zone-id>, plusroute53:GetChange. Create one access key.ployz-hosted-dns, separate from Cloud because this is shared infrastructure. Empty serviceapi, region EU West (Amsterdam). The code comes later.dns.ployz.appto the service, and create the CNAME that Railway gives you in Route 53.3. PlanetScale Postgres
planetscale-nickpotts→ new Postgres databaseployz-hosted-dns· regioneu-central-1(Frankfurt) · smallest size.4. Reply here with
[-]Provision Hosted DNS infrastructure: apex domain, AWS, Route 53, EC2, PlanetScale[/-][+]Provision Hosted DNS infrastructure: apex domain, AWS, Route 53, Railway, PlanetScale[/+]nick-potts commentedon Sep 24, 2026
Decisions recorded while provisioning
These go into the resolution and into the Hosted DNS build spec.
ployz.appzone. This overrides "API runs on EC2" in Design the Hosted DNS service.eu-central-1(Frankfurt) and Railway EU West (Amsterdam).ployz.app, with the registration transferred from Cloudflare to Route 53. Cloudflare Registrar requires Cloudflare's nameservers.POST /domainsreturns503 capacity_exhaustedwhen(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.GetHostedZoneLimit(real count against the limit), alerts at 80%, and flags drift from the database count. It alerts through a log line or webhook.us-east-1, or add a second apex. The API already returns the granted name, so Cloud doesn't change.nick-potts commentedon Sep 25, 2026
Provisioned (2026-09-25)
Route 53
ployz.app, idZ01536093OYT3FLZSI80R, in account557003269662.ns-656.awsdns-18.net,ns-1771.awsdns-29.co.uk,ns-133.awsdns-16.com,ns-1029.awsdns-00.orgIN_PROGRESS(submitted 2026-09-24). Cloudflare had no live records and no DNSSEC, so there was nothing to migrate or turn off.dns.ployz.appCNAMEar34zafx.up.railway.app, plus Railway's_railway-verify.dnsTXT. Route 53 already serves both. They go live when the delegation moves.IAM
ployz-hosted-dns, no console access. Its inline policyroute53-ployz-appallowsChangeResourceRecordSets,ListResourceRecordSets,GetHostedZoneandGetHostedZoneLimiton the zone only, plusGetChange. Tested: the zone limit reads 2/10,000, andListHostedZonesis denied.Railway
ployz-hosted-dns(898f2c4a-5bbe-44fa-b3dd-10836cadbd66), in the Order Editing workspace next to Ployz Dashboard, environmentproduction.api(f8f9bc48-8a7e-46ac-ba4e-6fda87771a16), region EU West (Amsterdam). Empty, no source yet.AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_REGION=us-east-1,ROUTE53_HOSTED_ZONE_ID,HOSTED_DNS_APEX=ployz.app.dns.ployz.app→ port 8080. Its certificate waits on the delegation.Remaining
ployzadmin profile).ployz-hosted-dns,eu-central-1: create it and an app role. Store the URL asDATABASE_URLon Railwayapi.nick-adminbootstrap access key.nick-potts commentedon Sep 25, 2026
Provisioned, part 2 (2026-09-25)
Domain
ployz.appto the 4awsdnsNS, and Cloudflare's resolver already returnsdns.ployz.app→ar34zafx.up.railway.app. Railway's domain verification and certificate are waiting for its next check.PlanetScale
ployz, Postgres databaseployz-hosted-dns, regioneu-central(AWSeu-central-1), branchmain, PG 18.6.api(pscale_api_nu9peadyfinx…), which inheritspostgresso the service can run its own migrations. It never expires.aws-eu-central-1-2.pg.psdb.cloud:5432, databasepostgres,sslmode=verify-full. The URL is stored only in RailwayapiasDATABASE_URL. Tested: connect, thenCREATE/DROP TABLE.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)
publicca.googleapis.com, create an EAB key, and storeGTS_EAB_KIDandGTS_EAB_HMACin Railwayapi.nick-adminbootstrap key.nick-potts commentedon Sep 25, 2026
Resolution
Hosted DNS infrastructure is provisioned. Details: part 1, part 2, and the decisions.
ployz.app. Registrar Route 53 (moved from Cloudflare), expires 2028-03-26, auto-renew and transfer lock onZ01536093OYT3FLZSI80R, account557003269662, 4awsdnsNS delegatedployz-hosted-dns(898f2c4a…), serviceapi(f8f9bc48…), EU West,https://dns.ployz.app→ port 8080, certificate validployz-hosted-dns, limited to the zone. Its key exists only in Railway variablesployz, Postgresployz-hosted-dns,eu-central-1, roleapi. The URL exists only in RailwayDATABASE_URLployz-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:
GetHostedZoneLimitalert at 80%.Credentials live in Railway variables (service
api). The admin bootstrap is the local AWS profileployz(IAM usernick-admin); deactivate its key once nothing needs admin.