doomain
v0.1.16
Published
Configure Vercel and Clerk production domains in seconds
Readme
Doomain
Doomain links Vercel projects and first-time Clerk production instances to custom domains from your terminal.
It handles the boring parts of custom-domain setup: selecting the Vercel project, finding the right DNS zone, adding the domain to Vercel, writing the Vercel DNS records, waiting for public DNS propagation, and asking Vercel to verify the domain.
Use the interactive wizard when working by hand. Use explicit commands with --json for scripts, CI, or agents.
Features
- Interactive Vercel domain-linking wizard.
- Script-friendly commands with one JSON object on stdout.
- Vercel project detection from
.vercel/project.json. - DNS provider inference by longest matching configured zone.
- Dry-run plans before writing changes.
- Safety checks before replacing DNS records that point elsewhere.
- DNS propagation and Vercel verification wait loop.
- DNS provider support for Spaceship, Namecheap, Cloudflare, and Hostinger.
- First-time Clerk production setup with automatic CNAME configuration.
Install
npm install -g doomainDoomain requires Node.js 18 or newer.
Quick Start
Run the wizard:
doomainThe wizard will:
- Ask for a Vercel token if one is not already configured.
- Let you choose a Vercel personal account or team.
- Detect and preselect a local Vercel project when
.vercel/project.jsonexists. - Connect a DNS provider if none is configured.
- List domains from configured DNS providers.
- Preview the Vercel and DNS changes.
- Warn if the current DNS target points elsewhere and ask before overriding it.
- Apply the changes and request Vercel verification.
If you already know the target project and domain, run the link command directly:
doomain link app.example.com --project my-vercel-projectScripted Setup
For CI, shell scripts, and agents, use explicit commands and --json.
First save Vercel credentials:
doomain auth vercel \
--token "$VERCEL_TOKEN" \
--team-id "$VERCEL_TEAM_ID" \
--json--team-id is optional for personal-account usage.
Then connect one DNS provider:
doomain providers connect cloudflare \
--credential apiToken="$CLOUDFLARE_API_TOKEN" \
--credential accountId="$CLOUDFLARE_ACCOUNT_ID" \
--jsonPreview the domain link:
doomain link app.example.com --project my-vercel-project --dry-run --jsonApply it:
doomain link app.example.com --project my-vercel-project --jsonSet up a Clerk application's first production instance and primary domain:
doomain auth clerk --platform-api-key "$CLERK_PLATFORM_API_KEY" --app app_123 --json
doomain clerk domains add example.com --app app_123 --jsonCreate a Platform API key from the Clerk Dashboard API keys page. Platform keys start with ak_; Clerk instance secret keys (sk_) cannot create a production instance.
This Clerk command intentionally aborts when the application already has a production instance. Change existing production domains manually in the Clerk Dashboard or with Clerk CLI.
If JSON mode returns DNS_TARGET_CONFLICT, the current DNS target appears to point to another project or site. Re-run with --force only when you intend to replace that DNS target.
Provider Setup
Doomain stores local credentials in ~/.doomain/config.json with 0600 file permissions. Environment variables override saved config values.
| Provider | Provider ID | Required credentials | Environment variables | Notes |
| --- | --- | --- | --- | --- |
| Spaceship | spaceship | apiKey, apiSecret | SPACESHIP_API_KEY, SPACESHIP_API_SECRET | API key needs domain read access and DNS record read/write access. |
| Namecheap | namecheap | apiUser, apiKey, clientIp | NAMECHEAP_API_USER, NAMECHEAP_API_KEY, NAMECHEAP_CLIENT_IP | API access must be enabled and clientIp must be your whitelisted public IPv4. Optional: username, sandbox. |
| Cloudflare | cloudflare | apiToken, accountId | CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID | API token needs Zone:Read and DNS:Edit. Vercel records are written as DNS-only records, not proxied. |
| Hostinger | hostinger | apiToken | HOSTINGER_API_TOKEN | API token needs access to domain portfolio and DNS zone records. |
Spaceship
doomain providers connect spaceship \
--credential apiKey=spaceship_key \
--credential apiSecret=spaceship_secretCompatibility aliases are also available:
doomain providers connect spaceship --api-key spaceship_key --api-secret spaceship_secretNamecheap
doomain providers connect namecheap \
--credential apiUser=your_namecheap_user \
--credential apiKey=your_api_key \
--credential clientIp=your_whitelisted_ipv4For Namecheap sandbox testing:
doomain providers connect namecheap \
--credential apiUser=your_sandbox_user \
--credential apiKey=your_sandbox_key \
--credential clientIp=your_whitelisted_ipv4 \
--credential sandbox=trueNAMECHEAP_USERNAME is optional. If omitted, Doomain uses apiUser as the Namecheap username.
Cloudflare
doomain providers connect cloudflare \
--credential apiToken=your_cloudflare_api_token \
--credential accountId=your_cloudflare_account_idCloudflare records created for Vercel or Clerk A, AAAA, and CNAME targets are set to proxied: false so the service can validate the domain.
Hostinger
doomain providers connect hostinger \
--credential apiToken=your_hostinger_api_tokenCreate Hostinger API tokens from hPanel Account > API. Doomain lists active zones from the Hostinger domain portfolio and updates records through the DNS zone API.
Linking Domains
You can pass the full target domain as a positional argument:
doomain link app.example.com --project my-appOr pass a base domain and subdomain:
doomain link --domain example.com --subdomain app --project my-appLink the apex/root domain:
doomain link --domain example.com --apex --project my-appAn exact zone match without --subdomain is also treated as apex:
doomain link example.com --project my-appPreview without writing to Vercel or DNS:
doomain link app.example.com --project my-app --dry-run --jsonSkip the DNS/Vercel verification wait:
doomain link app.example.com --project my-app --no-waitChange the wait timeout, in seconds:
doomain link app.example.com --project my-app --timeout 600Use a specific DNS provider instead of provider inference:
doomain link app.example.com --project my-app --provider cloudflareUse -p as shorthand for --project:
doomain link app.example.com -p my-appClerk Production Domains
doomain clerk domains add mirrors Clerk CLI's initial production deployment API. It creates a production instance by cloning the application's development instance, sets the requested primary domain, writes every CNAME returned by Clerk, and optionally waits for Clerk's DNS, SSL, and email DNS status.
doomain clerk domains add example.com --app app_123Use --dry-run to verify that the application has no production instance and that Doomain can resolve the DNS zone. Clerk only returns the exact CNAME records after production is created, so they are not included in the dry-run result.
If a production instance already exists, the command returns CLERK_PRODUCTION_EXISTS without changing DNS. Doomain does not automate production-domain migrations because Clerk domain changes can cause downtime and require publishable-key, OAuth redirect, and deployment updates.
After creation, the result includes Clerk CLI follow-up commands. Run them to link the local project, pull production keys, finish production OAuth configuration, and verify provisioning:
clerk link --app app_123
clerk env pull --app app_123 --instance prod
clerk deploy
clerk deploy statusWhat Gets Created
For apex/root domains, Doomain creates Vercel's apex A record:
A @ 76.76.21.21For subdomains, Doomain creates a CNAME to Vercel's recommended target. If Vercel does not return a special target, it uses:
CNAME app cname.vercel-dns.comDuring real linking, Doomain also reads Vercel's domain response and adds any required TXT verification records, for example:
TXT _vercel vc-domain-verify=app.example.com,...Project Detection
--project accepts a Vercel project id or name. If you omit it, Doomain searches upward from the current directory for .vercel/project.json and uses its projectId.
The interactive wizard also uses .vercel/project.json, but only preselects the local project when the detected orgId matches the selected Vercel account or team.
Provider And Zone Inference
When --provider is omitted, Doomain lists zones from every configured provider and chooses the longest zone that matches the target domain.
Example: for api.dev.example.com, a provider zone named dev.example.com wins over example.com.
If two providers have the same best matching zone, Doomain stops with PROVIDER_ZONE_AMBIGUOUS. Re-run with --provider to choose one:
doomain link app.example.com --project my-app --provider cloudflareSafety, Dry Runs, And Conflicts
Use --dry-run before applying changes:
doomain link app.example.com --project my-app --dry-run --jsonDry runs do not write to Vercel or DNS. They resolve the target project, provider, zone, and base Vercel DNS record. They do not add the domain to Vercel, fetch Vercel TXT verification records, or inspect current DNS records for conflicts.
DNS conflict rules:
- Existing exact records are skipped.
- TXT records can coexist at the same name.
- Same-name, same-type conflicts trigger an interactive override prompt or require
--forcein JSON/non-interactive mode. - CNAME slot conflicts trigger an interactive override prompt or require
--forcein JSON/non-interactive mode because a CNAME cannot share a name with most other record types.
For real links, Doomain inspects the target A or CNAME DNS slot before adding the domain to Vercel. In interactive mode, it shows the existing and desired records and asks whether to override. In JSON mode, it fails with DNS_TARGET_CONFLICT instead of prompting.
Use --force only when you intend to replace conflicting DNS records or move an existing Vercel alias:
doomain link app.example.com --project my-app --force--force can remove an existing Vercel alias from another project and add it to the target project.
Confirming the interactive DNS override only forces DNS writes. If Vercel says the domain is already assigned to another project, re-run with --force to move that Vercel alias.
Namecheap note: Namecheap's API writes DNS through setHosts, which replaces the full host list. Doomain reads all existing records first, applies planned changes in memory, preserves unrelated records, then submits the complete final record set.
JSON And Agent Usage
Use --json for automation. JSON mode never prompts and writes exactly one JSON object to stdout.
Successful commands use this shape:
{"ok":true,"data":{}}Failed commands use this shape:
{"ok":false,"error":{"code":"MISSING_ARGUMENT","message":"Domain is required."}}JSON mode is also enabled automatically when stdout is not a TTY, which makes piped commands script-safe.
Use explicit commands for agents. The bare doomain command is interactive, and doomain --json returns an error that points agents to doomain link --json.
Useful agent-safe commands:
doomain link app.example.com --project my-app --json
doomain providers list --json
doomain providers status --no-verify --json
doomain domains list --provider cloudflare --domain example.com --json
doomain projects list --search my-app --json
doomain clerk domains add example.com --app app_123 --json
doomain schema --json
doomain schema link --jsonThe schema command prints machine-readable metadata for the documented command contracts:
doomain schema --json
doomain schema "providers connect" --jsonCommand Reference
Run doomain help <command> for oclif-generated help.
doomain
Starts the interactive wizard.
doomaindoomain link [domain]
Links a Vercel project to a domain and creates DNS records.
Common flags:
--domain <domain>: target domain or base zone.--subdomain <name>: subdomain under--domain.--apex: use the root/apex domain.-p, --project <project>: Vercel project id or name.--provider <id>: DNS provider id.--dry-run: preview without writing.--force: overwrite DNS conflicts without prompting and allow Vercel alias moves.--wait,--no-wait: wait for DNS and Vercel verification. Default is--wait.--timeout <seconds>: wait timeout. Default is300.--json: output one JSON object.
Examples:
doomain link app.example.com --project my-app
doomain link --domain example.com --subdomain app --project my-app
doomain link --domain example.com --apex --project my-app
doomain link app.example.com --project my-app --dry-run --json
doomain link app.example.com --project my-app --forcedoomain auth vercel
Saves Vercel credentials locally.
doomain auth vercel --token vercel_token
doomain auth vercel --token vercel_token --team-id team_123 --jsondoomain auth logout vercel
Removes saved Vercel credentials from the local config file.
doomain auth logout vercel
doomain auth logout vercel --jsonIf VERCEL_TOKEN or VERCEL_TEAM_ID are still set, they continue to override local config.
doomain auth clerk
Saves and verifies a Clerk Platform API key and default application id.
doomain auth clerk --platform-api-key ak_123 --app app_123doomain auth logout clerk
Removes saved Clerk credentials. CLERK_PLATFORM_API_KEY and CLERK_APPLICATION_ID continue to override local config when set.
doomain clerk domains add <domain>
Creates the application's first Clerk production instance and primary domain, then configures Clerk's returned CNAME records.
doomain clerk domains add example.com --app app_123
doomain clerk domains add example.com --app app_123 --dry-run --json
doomain clerk domains add example.com --app app_123 --no-wait --jsondoomain providers list
Lists supported DNS providers.
doomain providers list
doomain providers list --jsondoomain providers connect [provider]
Saves DNS provider credentials locally.
doomain providers connect cloudflare -c apiToken=token -c accountId=account_id
doomain providers connect namecheap -c apiUser=user -c apiKey=key -c clientIp=127.0.0.1 --json
doomain providers connect hostinger -c apiToken=token --json
doomain providers connect spaceship --api-key key --api-secret secretCommon flags:
-c, --credential key=value: provider credential. Can be repeated.--api-key <key>: Spaceship compatibility alias forapiKey.--api-secret <secret>: Spaceship compatibility alias forapiSecret.--no-verify: save credentials without calling the provider API first.--json: output one JSON object.
doomain providers add [provider]
Alias for providers connect.
doomain providers add cloudflaredoomain providers status
Shows configured provider health.
doomain providers status
doomain providers status --no-verify --jsondoomain providers verify <provider>
Verifies saved provider credentials.
doomain providers verify cloudflare
doomain providers verify namecheap --jsondoomain providers disconnect <provider>
Removes saved DNS provider credentials locally. providers logout is an alias.
doomain providers disconnect cloudflare
doomain providers logout namecheap --jsonEnvironment variables for that provider still override local config after disconnect.
doomain domains list
Lists DNS zones and records.
doomain domains list --provider cloudflare
doomain domains list --provider cloudflare --domain example.com --jsonIf --provider is omitted, this command uses DOOMAIN_PROVIDER, then the saved default provider, then spaceship.
doomain projects list
Lists Vercel projects for the configured Vercel account or team.
doomain projects list
doomain projects list --search my-app --jsondoomain verify
Asks Vercel to verify a project domain without changing DNS.
doomain verify --domain example.com --subdomain app --project my-app
doomain verify --domain example.com --apex --project my-app --jsonFor verify, pass a base domain plus --subdomain, or pass a base domain plus --apex.
doomain schema [command]
Prints machine-readable command metadata.
doomain schema --json
doomain schema link --json
doomain schema "providers connect" --jsonEnvironment Variables
Vercel:
VERCEL_TOKEN
VERCEL_TEAM_IDClerk:
CLERK_PLATFORM_API_KEY
CLERK_APPLICATION_IDSpaceship:
SPACESHIP_API_KEY
SPACESHIP_API_SECRETNamecheap:
NAMECHEAP_API_USER
NAMECHEAP_API_KEY
NAMECHEAP_USERNAME
NAMECHEAP_CLIENT_IP
NAMECHEAP_SANDBOXCloudflare:
CLOUDFLARE_API_TOKEN
CLOUDFLARE_ACCOUNT_IDHostinger:
HOSTINGER_API_TOKENDoomain defaults and config:
DOOMAIN_DOMAIN
DOOMAIN_PROVIDER
DOOMAIN_CONFIG_DIR
DOOMAIN_CONFIG_FILE
DOOMAIN_DEBUGNotes:
DOOMAIN_DOMAINis used by the interactive wizard and internal link planning, but thedoomain linkcommand currently still requires a positional domain or--domainbefore it calls the linker.DOOMAIN_PROVIDERis used by the interactive wizard anddomains list. Forlink, pass--providerwhen you want to force a specific provider.- Set
DOOMAIN_DEBUG=1to enable provider debug mode where supported. - Use
DOOMAIN_CONFIG_FILEin tests or scripts when you want isolated credentials.
Troubleshooting
Missing Vercel token
Run doomain auth vercel or set VERCEL_TOKEN.
No DNS provider is configured
Run doomain providers connect <provider> or set the provider's required environment variables.
PROVIDER_ZONE_NOT_FOUND
The selected provider does not have a DNS zone matching the target domain. Check doomain domains list --provider <provider> or pass the correct --provider.
PROVIDER_ZONE_AMBIGUOUS
More than one configured provider has the same best matching zone. Re-run with --provider cloudflare, --provider namecheap, --provider spaceship, or --provider hostinger.
Namecheap authentication or permission errors
Make sure Namecheap API access is enabled and your current public IPv4 is whitelisted in Namecheap API Access settings.
Cloudflare permission errors
Make sure the API token has Zone:Read and DNS:Edit permissions for the account that owns the zones.
Hostinger authentication errors
Make sure the API token is active and can access the domains you want Doomain to manage.
Hostinger DNS zone not found
Make sure the domain is active in Hostinger before linking it. Domains shown as pending_setup in Hostinger's portfolio API are not writable through the DNS zone API yet.
DNS propagation timeout
The DNS records may have been saved even if Vercel verification timed out. Check the domain in Vercel, inspect records with doomain domains list, or re-run verification with doomain verify.
DNS target conflict
The domain already has a conflicting A or CNAME record, which usually means it points to another project or site. In interactive mode, confirm the override only if you intend to replace that target. In JSON mode, re-run doomain link with --force to overwrite DNS.
Domain already assigned to another Vercel project
If you intend to move it, re-run doomain link with --force. This can remove the alias from the previous Vercel project.
SSL certificate is not ready yet
Vercel may need a few extra minutes to provision SSL after the domain verifies.
Development
This repository is a TypeScript ESM oclif CLI package.
bun install --frozen-lockfile
bun run build
bun run testUseful commands:
bun run lint
bun run check
bun run format
bunx mocha --forbid-only "test/path/to-file.test.ts"
./bin/dev.js link app.example.com --project my-app --dry-runNotes for contributors:
- Source commands live in
src/commands/**. - Shared logic lives in
src/lib/**. - Build output goes to
dist/; do not editdistdirectly. examples/**is excluded from this package's Biome surface.- Public command contract metadata lives in
src/lib/command-schema.ts. prepackrunsoclif manifest && oclif readme, which may update generated README command docs.
License
MIT
