Skip to main content

Overview

The Domain resource manages DNS infrastructure declared in TypeScript. Each domain lives in fjall/domains/<zone>/infrastructure.ts as a typed Domain construct. The file you edit is the file that deploys. A Domain creates Route53 hosted zones, DNS records, ACM certificates, and cross-account delegation roles. BIND zone files are an interchange format only. fjall domain import --from-bind reads them and fjall domain export writes them, but the declaration surface is always infrastructure.ts.

Quick Start

fjall domain deploy <domain> is the only domain-deploy spelling. fjall deploy takes application names only.

The Domain Construct

A complete declaration, in the shape fjall domain create scaffolds:

Registrar modes

registrar is the discriminant of a typed union. Each mode accepts different props. route53 creates the hosted zone. Set hostedZoneId to adopt a live zone by reference instead, which leaves the zone resource itself outside CloudFormation. Adopted zones go through the record-takeover ceremony on first deploy (see Lifecycle). external-delegated declares a child zone, delegatedSubdomain under zoneName. When Fjall manages the parent, the CLI injects parentDelegationRoleArn at deploy time and the child writes its own NS records into the parent zone, across accounts. phase: "zone" | "full" is the two-step guard the CLI drives automatically. Zone and delegation deploy first, certificates only after the delegation has propagated. Issuing certificates too early leaves ACM DNS validation hanging until CloudFormation rolls back. Adopt an existing live child zone with hostedZoneId plus adoptedNameServers, set both together or neither. adoptedNameServers declares the zone’s already-live NS set, so the parent delegation is written and verified against the adopted zone rather than a newly minted one. external-records creates no hosted zone, so you create every record by hand at the DNS provider that serves the names. Domain.manualRecords lists the records you declare, numbered by their position in the list. The stack emits a <Zone>ManualRecord<n>Name, <Zone>ManualRecord<n>Type and <Zone>ManualRecord<n>Value triple per record. <Zone> is the zone name with its dots removed and PascalCased (example.com becomes Examplecom, my-site.com becomes MySitecom), and <n> is the record’s position in records.
From fjall 38 those outputs describe the declared records only. Through 37.x an external-records domain also emitted one output triple per certificate name, ahead of the declared records, so the next deploy after upgrading removes those and renumbers the record outputs from 0. It changes outputs only, no resource is replaced, but anything reading <Zone>ManualRecord<n>Value by index must re-read it.
Domain.manualRecords and the <Zone>ManualRecord<n>Type output carry A, AAAA, CNAME and TXT only. A declared MX, NS, SRV or CAA record is reported as TXT, and an array value is joined into one comma-separated string. Create those four types from your infrastructure.ts declaration, not from the reported triple. Neither Domain.manualRecords nor those outputs describe a certificate validation record. ACM chooses each one, _<hash>.<name> CNAME _<hash>.<id>.acm-validations.aws., only when CloudFormation creates the certificate, so none exists at synth. CloudFormation then waits on the certificate until its record resolves publicly. While it waits, fjall domain deploy reads the records from ACM and announces each one as a warning. A deploy that fails while ACM is still waiting repeats the outstanding records in its error. CloudFormation keeps waiting after the command exits, so create the records, then re-run the deploy once the stack has settled. fjall domain verify reports what ACM still awaits at any time (see Verify delegation). The reads need cloudformation:DescribeStacks, cloudformation:ListStackResources, cloudformation:DescribeStackEvents and acm:DescribeCertificate. A denied read reports the certificate check as unavailable and names all four. Outside the CLI, from a principal that holds cloudformation:DescribeStackEvents and acm:DescribeCertificate, find the certificate’s ARN in the domain stack’s events and ask ACM directly in the stack’s region:
Keep each validation record in place after the certificate is issued, because ACM re-reads it at renewal.

Records

records accepts two shapes. Standard records cover A, AAAA, CNAME, MX, TXT, NS, SRV, CAA:
value takes a string or an array of strings. ttl is optional. Alias records are A or AAAA records pointing at an AWS target. They take no ttl, because Route53 aliases inherit the target’s:
Naming an application that publishes no matching export fails at CloudFormation execution, after synth and every unit test have passed.

Record names

Record names follow BIND semantics.

Record construct IDs

recordIds controls how each record’s CloudFormation logical ID is derived. Under "stable" the composer refuses duplicate (name, type) pairs and sanitised-name collisions at synth. Set a per-record id to break a collision. Flipping an already-deployed zone from "indexed" to "stable" renames every record’s logical ID, which deploys as delete plus recreate of live record sets. Treat it as a deliberate two-deploy migration, not a casual edit.

Certificates

Each entry becomes a DNS-validated ACM certificate. An entry with cloudFront: true mints its certificate in us-east-1. That happens in-stack when the domain stack already resolves to us-east-1, otherwise via the paired us-east-1 certificate stack. It publishes the <zone>-us-east-1-certificate-arn stack export alongside the regional <zone>-certificate-arn, and the managed domain binding (below) carries both ARNs to consuming applications. At most one cloudFront certificate per Domain, so put extra hostnames in subjectAlternativeNames. Registrar external-records does not support it, because it has no hosted zone to validate against.

Lifecycle

Every destructive path (takeover, residue deletion, destroy) runs the destruction ceremony. A ticket names each affected record or zone, you consent per row with --remediate, and withheld consent exits 4 with nothing mutated. Records classified unknown are never offered for deletion. After a deploy, run fjall domain verify example.com to check propagation. For a route53 or external-delegated domain it reports the domain, whether it is delegated to Route53, and the zone’s nameservers. An external-records domain skips the nameserver lookup and prints a DNS line in place of the delegation verdict, because Fjall creates no hosted zone for it. See fjall domain for the full verb and flag reference.

Application Binding

When Fjall manages a domain, application deploys receive a managed domain binding. The CLI resolves the domain stack’s outputs at deploy time and injects literal values: hosted zone ID, regional certificate ARN, us-east-1 certificate ARN for CloudFront, and the delegation role ARN. Literal values cross accounts and regions, so an application in another account binds to the zone with no extra wiring.
For bare-CDK synth outside the CLI, use the export-name form managedDomain: { zoneName, hostedZoneIdExport, certificateArnExport }. It resolves through Fn.importValue(), which works in the same account and same region only.

Configuration

Fjall tracks managed domains in fjall-config.json under the domains field. fjall domain create and fjall domain import register entries automatically. A domain nested under an already-registered domain becomes delegated with the longest-suffix parent, everything else is apex.
Every successful fjall domain deploy records account and region automatically, for apex and delegated entries alike. Pin them up front with fjall domain create --account and --region, or add them by hand. Record classification reads account to gather a child zone’s cross-account evidence, and region tells the child-account clients which region to search for the child stack.

Organisation Integration

Domains deploy automatically during fjall org deploy, in cascade order:
Apex domains deploy sequentially, because delegation roles must exist first. Delegated children then deploy in parent-first waves, running in parallel within each wave. An external-records domain that declares certificates holds the Domains phase: CloudFormation waits for validation records only you can create. The cascade announces each record as a warning the same way fjall domain deploy does, and a domain deploy that fails while ACM is still waiting repeats them (see Registrar modes).

Importing an existing zone

Import curates the declaration rather than copying the zone wholesale. Records owned by other live CloudFormation stacks are deliberately left undeclared, with a file header explaining each omission. Typical examples are an apex alias managed by an application stack, validation records for live certificates, and a delegated child’s NS record. What remains is exactly what this Domain construct should own. Satellite ownership is proven from live evidence: CloudFormation stack templates, including Custom::CrossAccountZoneDelegation delegation resources, and ACM certificate ownership. The proof crosses accounts. A delegated child zone living in another AWS account classifies its parent-side NS row as satellite when its domain stack is live, claims the NS record via a literal DelegatedZoneName, and publishes a nameservers output matching the live NS values (order, case, and trailing-dot insensitive). Anything short of full proof fails closed to unknown with a warning naming the child domain and the fix, never a guess. The classifier locates the child through the delegated domain’s account key in fjall-config.json (see Configuration). An unreachable child account affects only that child’s row, leaving the rest of the zone’s evidence intact.

Interactive import

fjall domain import --interactive collects records through prompts. The flow shows a preview of the curated infrastructure.ts, with satellite-owned records omitted by design, and writes nothing until you confirm the commit step.

Large zones

fjall domain import --from-route53 caps at 5,000 records. Beyond that, export the zone with fjall domain export (or the AWS CLI), trim the BIND file to the records this construct should own, then import it with --from-bind.

External DNS Registrar

For domains registered outside AWS, point your registrar’s nameservers at Route53:
  1. Run fjall domain create example.com.
  2. Run fjall domain deploy example.com.
  3. Run fjall domain verify example.com --show-delegation to print the registrar delegation instructions, including the nameservers.
  4. Update your registrar’s NS records to point at them.
For subdomain-only delegation, declare the child with registrar: "external-delegated" and create the NS records the deploy reports at your registrar. To keep all DNS at the external registrar, use registrar: "external-records". Its deploy announces the certificate validation records ACM asks for, which you create at the registrar, and fjall domain verify example.com lists any still outstanding. --show-delegation refuses an external-records domain, because it has no hosted zone to delegate to.

File Structure

Next Steps

fjall domain

Every domain verb, flag, and exit code.

Destructive Changes

The destruction ceremony and named consents.

Static Site Pattern

Serve a static site on your domain via CloudFront.

Deploy

Ship your domains and applications to AWS with the Fjall CLI.