Docs
Operations
Configuration, OIDC providers, availability semantics, production and Docker.
Configuration and deployment notes for running domainhawk.
Configuration
All settings are environment variables. Copy .env.example and adjust.
Application
| Variable | Default | Description |
|---|---|---|
DH_APP_URL |
http://127.0.0.1:8090 |
Public base URL. Required for OIDC redirects, cookies and email links. |
DH_VERSION |
dev |
Shown in the footer and health response. |
DH_LOG_LEVEL |
info |
Minimum log level: debug, info, warn or error (case-insensitive). Applied to the structured logger and PocketBase's logger at startup. |
DH_LOG_FORMAT |
json |
Log output encoding: json for structured logs or text for logfmt. |
DH_PASSWORD_AUTH |
true |
Enable email/password login. Set false for SSO-only. |
DH_SESSION_SECRET |
random per boot | HMAC key for the OIDC state cookie. Set a stable random value (32+ bytes) for multiple replicas. |
DH_REGISTRATION_ENABLED |
false |
Allow self-service account creation. Requires DH_PASSWORD_AUTH=true. |
DH_ADMIN_EMAIL |
empty | Support inbox for Help tickets. Empty disables the support form. |
DH_REGISTRAR_URL_TEMPLATE |
https://porkbun.com/checkout/search?q={domain} |
"Buy" link. Must contain {domain}. |
DH_SEED_EMAIL / DH_SEED_PASSWORD |
empty | Optional local bootstrap account. Development only; both must be set. |
OIDC
Set all three of DH_OIDC_ISSUER, DH_OIDC_CLIENT_ID and
DH_OIDC_CLIENT_SECRET to enable OIDC. Leave them empty for password-only.
| Variable | Default | Description |
|---|---|---|
DH_OIDC_ISSUER |
empty | Issuer URL; discovery uses ${issuer}/.well-known/openid-configuration. |
DH_OIDC_CLIENT_ID |
empty | Confidential client ID. |
DH_OIDC_CLIENT_SECRET |
empty | Client secret. |
DH_OIDC_DISPLAY_NAME |
SSO |
Button label: "Continue with …". |
DH_OIDC_SCOPES |
openid email profile |
Space-separated; must include openid, and email is required to create/link users. |
Register these redirect URIs:
${DH_APP_URL}/auth/callback— the HTMX UI.${DH_APP_URL}/api/oauth2-redirect— optional, for PocketBase API clients.
The provider must return a verified email (email_verified: true). Users are
provisioned on first login by OIDC subject, with fallback linking by verified
email.
Scheduler, RDAP and WHOIS
| Variable | Default | Description |
|---|---|---|
DH_CHECK_INTERVAL |
4h |
Minimum time between checks of one entry. |
DH_CHECK_TIMEOUT |
20s |
Per-domain check timeout. |
DH_CHECK_CONCURRENCY |
3 |
Parallel checks during a sweep. |
DH_MAX_DOMAINS_PER_USER |
100 |
Maximum watched domains per user. |
DH_MAX_ADDS_PER_DAY |
100 |
Maximum adds per rolling 24 hours, from an internal log (deleting does not reset it). |
DH_API_RATE_LIMIT |
120 |
Requests per minute per API token. |
DH_CRON_SCHEDULE |
*/15 * * * * |
Scheduler wake-up; checks entries older than the interval. |
DH_CRON_ENABLED |
true |
Disable the scheduler entirely. |
DH_STARTUP_SWEEP |
true |
Run one due sweep shortly after startup. |
DH_RDAP_BOOTSTRAP_URL |
IANA dns.json |
Override for testing or a mirror. |
DH_RDAP_TIMEOUT |
10s |
HTTP timeout for bootstrap and registry requests. |
DH_RDAP_CACHE_TTL |
1h |
Per-domain cache; manual force checks bypass it. |
DH_RDAP_USER_AGENT |
domainhawk/<version> |
User agent sent to RDAP servers. |
DH_WHOIS_ENABLED |
true |
Fallback for TLDs without RDAP. |
DH_WHOIS_TIMEOUT |
10s |
WHOIS connect/read timeout. |
DH_WHOIS_IANA_ADDRESS |
whois.iana.org:43 |
IANA referral server. |
The cron wakes every 15 minutes, selects entries with no check or a
last_checked older than DH_CHECK_INTERVAL, and checks them with bounded
concurrency. A domain shared by several users is fetched once per cache TTL.
Check now bypasses the cache but is limited to once per day per entry.
Email notifications
Notifications are enabled by default but only send when SMTP is configured,
either via the environment below or the PocketBase dashboard
(Settings → Mail settings). Environment values overwrite dashboard settings at
startup.
| Variable | Default | Description |
|---|---|---|
DH_NOTIFICATIONS_ENABLED |
true |
Global switch. |
DH_SMTP_HOST |
empty | SMTP host; setting this configures the SMTP client. |
DH_SMTP_PORT |
587 |
SMTP port. |
DH_SMTP_USERNAME |
empty | Leave empty for an unauthenticated relay. |
DH_SMTP_PASSWORD |
empty | SMTP password. |
DH_SMTP_AUTH_METHOD |
PLAIN |
PLAIN or LOGIN. |
DH_SMTP_TLS |
false |
true = implicit TLS (usually 465); false = plaintext with opportunistic STARTTLS (usually 587). |
DH_SMTP_LOCAL_NAME |
empty | EHLO/HELO name for relays that require it. |
DH_MAIL_FROM |
domainhawk@localhost |
From address. |
DH_MAIL_FROM_NAME |
domainhawk |
From display name. |
One availability email is sent per availability window per entry. Expiry alerts
are sent once per threshold — when a name first falls within 30, 7 and 1 days of
the stored expiry date — and drop alerts once per distinct pending delete or
redemption period status. Failed delivery is retried on the next sweep because
notification markers are only written after a successful send. A transient
unknown result does not reopen the availability window or erase the last known
registry details. Each entry has separate availability and expiry/drop
preferences on its Details page.
OIDC provider examples
Keycloak: confidential client, Standard Flow enabled, PKCE required (S256).
Redirect URI ${DH_APP_URL}/auth/callback; issuer
https://keycloak.example/realms/domainhawk.
Authentik: OAuth2/OpenID provider with the same redirect URI; issuer
https://authentik.example/application/o/domainhawk/.
Auth0: Regular Web Application with the same redirect URI; issuer
https://TENANT.eu.auth0.com/.
Availability semantics
RDAP is authoritative for whether a registration record exists, but it is not a purchase quote:
404from the authoritative registry with a valid RDAP error body → available.- A domain object response → registered / taken.
- No RDAP service for the TLD → query the registry's WHOIS service. WHOIS is
trusted only for explicit answers such as DENIC's
Status: free/Status: connect; ambiguous or rate-limited responses stay unknown. - Rate limits, registry errors, malformed responses, non-RDAP
404HTML or no WHOIS service → unknown. Unknown never becomes available, which avoids false positives. - Reserved, premium and registry-locked names may still be unavailable even without a registration record. Confirm with a registrar before buying.
WHOIS is plaintext and registry-specific: a best-effort fallback, not a replacement for RDAP.
Drop Radar metadata
RDAP domain objects may also include registration and expiry events, registrar,
status codes and nameservers. domainhawk stores these as best-effort metadata
and shows them on the entry's Details page. They do not change availability:
only an authoritative 404 registration record makes a domain available, and
pending delete or redemption period still means the domain is registered.
Not every registry publishes an expiry date. DENIC's .de WHOIS service, for
example, exposes registration state but not the expiry date, so those entries
show Expiry not published and do not send expiry alerts. A renewal moves the
expiry date; domainhawk resets the reminder cycle when the date moves later by
more than a day.
Expiry and drop emails are enabled per entry on the Details page. They are separate from the availability email preference, so an existing watchlist entry does not start receiving new kinds of mail until its owner opts in.
Production notes
- Reverse proxy: terminate TLS in front of domainhawk and set
DH_APP_URLto the public HTTPS URL so cookies becomeSecure. Configure PocketBase's trusted proxy headers so rate limiting sees real client IPs. - Persistence: back up
pb_data(SQLite database, settings, uploads); PocketBase also exposes dashboard backups. - Scaling: the scheduler and in-memory RDAP cache assume one instance. For
several replicas, run the scheduler in one and share the cache or accept
duplicate polls. Set
DH_SESSION_SECRETidentically. - Health:
GET /healthzreturns JSON and checks the database. - Security: HttpOnly, SameSite=Lax session cookies; OIDC state, nonce and
PKCE S256; origin-checked UI mutations; rate-limited password login; enabled
PocketBase API rate limits; CSP/frame/referrer headers. Public signup is
disabled unless
DH_REGISTRATION_ENABLED=true; signed-in users can manage their own email and password. Other user mutation is disabled. Logout rotates the token key and revokes sessions; password reset, password change and email change also rotate the token key and deactivate every API token owned by the account. Direct PocketBase mutations ofwatchlist,api_tokens,email_codesandsupport_ticketsare disabled; API tokens may expire or be unlimited. - Check all: the watchlist header action respects the once-per-day limit per entry and reports skipped entries.
- MFA: the HTMX login flow does not implement MFA. Leave MFA disabled on the
userscollection (the default). - User provisioning: OIDC users are created on first login. Email linking
requires an existing verified account; an unverified match is rejected
with a 403 and must be resolved by an administrator. When
DH_REGISTRATION_ENABLED=truevisitors can create password accounts; they must verify their email with a one-time code before signing in. Password reset and email changes also use email codes, so configure SMTP.
Docker
make docker-build
docker run --rm -p 8090:8090 \
-v domainhawk_data:/app/pb_data \
-e DH_APP_URL=http://localhost:8090 \
domainhawk:dev
The image runs as UID 10001. Use a named volume (as above) or
chown -R 10001:10001 ./pb_data when bind-mounting. Pass DH_* variables or an
--env-file for OIDC and SMTP.