Skip to content

Configuration Reference

All infrastructure configuration lives in Pulumi.<stack-name>.yaml. See Pulumi.example.yaml for a fully documented reference with all available options.

Required Settings

Config Key Description Example
hawk:domain Internal domain for services hawk.example.com
hawk:publicDomain Public domain for DNS zones example.com
hawk:primarySubnetCidr VPC CIDR block 10.0.0.0/16

Domain & DNS

Hawk's services live on subdomains of hawk:domain (e.g. api.hawk.example.com) and need DNS delegation working before ACM TLS certificates can validate. Four paths — pick one before deploying:

Option When What to set
A. Route 53 Domains New setup, simplest path. AWS handles registration, hosted zone, and delegation in a single step. hawk:createPublicZone: "false" (the default) — Pulumi looks up the existing zone created by registration.
B. Existing registrar + manual delegation You already own the domain at Namecheap/GoDaddy/etc. and want to keep it there. hawk:createPublicZone: "true". After pulumi up creates the zone, copy the four NS records into your registrar's nameserver settings.
C. Cloudflare automatic delegation Parent domain is in Cloudflare and you want a subdomain delegated to AWS automatically. See Cloudflare below.
D. HTTP-only (testing only) Smoke-testing without a real domain. hawk:skipTlsCerts: "true". Services reachable only via the raw ALB DNS name. Not for real use.

Cert-validation hang gotcha

With options B or C, if DNS isn't working when pulumi up runs, the wildcard ACM certificate validation will hang for ~75 minutes (default timeout) before failing. Get delegation in place first.

Don't set createPublicZone: \"true\" with Option A

If you registered the domain in Route 53 (Option A), it already created a public hosted zone and pointed the registrar's NS records at it. Setting createPublicZone: "true" makes Pulumi silently create a second zone with different NS records, put all records there, and ACM validation will hang ~75 minutes before failing because the registrar still points at the original zone. Recovery requires either manually updating the registrar's NS records (Console only — standard IAM users typically lack route53domains:UpdateDomainNameservers) or destroying the duplicate Pulumi-created zone and re-running with createPublicZone: "false".

Authentication

When hawk:oidcClientId is not set, Hawk provisions a Cognito user pool during pulumi up and uses it as the auth provider. Create your first user with scripts/dev/create-cognito-user.sh <stack> <email> after the deploy finishes.

To use your own OIDC provider (Okta, Auth0, etc.), set all three of these — Hawk will skip the Cognito setup:

Config Key Description Example
hawk:oidcClientId OIDC client ID your-client-id
hawk:oidcAudience OIDC audience for access tokens https://api.example.com
hawk:oidcIssuer OIDC issuer URL https://login.example.com/oauth2/default

Infrastructure Options

Config Key Default Description
hawk:eksK8sVersion 1.33 Kubernetes version for EKS
hawk:albIdleTimeout 3600 ALB idle timeout in seconds
hawk:albInternal false Set to true to make the ALB internal (requires VPN)
hawk:cloudwatchLogsRetentionDays 14 CloudWatch log retention

Optional Integrations

These are all disabled by default. Enable them in your stack config when needed.

Datadog

Monitoring, APM, and log forwarding:

hawk:enableDatadog: "true"
hawk:datadogSite: datadoghq.com

Requires a <env>/platform/datadog-api-key secret in AWS Secrets Manager.

Cloudflare

Option C from the Domain & DNS table above. Hawk creates NS records in your Cloudflare parent zone pointing to the Route 53 hosted zone, so you don't have to move nameservers manually.

hawk:publicDomain must be a subdomain of hawk:cloudflareParentDomain (Pulumi raises a ValueError otherwise).

hawk:createPublicZone: "true"
hawk:cloudflareZoneId: "your-zone-id"
hawk:cloudflareParentDomain: "example.com"
hawk:publicDomain: "hawk.example.com"

Before pulumi up, create a Cloudflare API token (Zone:DNS:Edit on the parent zone) and store it in AWS Secrets Manager:

aws secretsmanager create-secret \
  --name "<env>/platform/cloudflare-api-token" \
  --secret-string "<token>"

<env> defaults to your Pulumi stack name. The deploy will fail with a "secret not found" error if this isn't set up first.

Tailscale

VPN overlay for private service access:

Set hawk:albInternal: "true" and store a Tailscale auth key in AWS Secrets Manager. This makes all services accessible only through your Tailscale network.

CrowdStrike Falcon

Endpoint protection for EKS nodes and the Tailscale subnet router:

hawk:enableCrowdstrike: "true"

Requires a <env>/platform/crowdstrike secret in AWS Secrets Manager with:

{
  "cid": "YOUR-CUSTOMER-ID",
  "client_id": "YOUR-API-CLIENT-ID",
  "client_secret": "YOUR-API-CLIENT-SECRET",
  "base_url": "https://api.us-2.crowdstrike.com"
}

Setup:

  1. In the CrowdStrike Falcon console, go to Support and resources > API clients and keys and create a client with Sensor Download: Read scope.
  2. Copy your CID from Host setup and management > Deploy > Sensor downloads.
  3. Your base_url matches your Falcon console URL (e.g. falcon.us-2.crowdstrike.comhttps://api.us-2.crowdstrike.com).

When enabled, this installs the Falcon sensor on:

  • GPU nodes (AL2023) — via the Karpenter EC2NodeClass userData at instance boot
  • Tailscale subnet router (AL2023 ARM64) — via cloud-init at instance boot

The falcon-sensor DaemonSet for all EKS nodes (including Bottlerocket) requires the Falcon Images Download API scope, which is part of the Falcon Cloud Security with Containers add-on.

Budget Alerts

hawk:budgetLimit: "10000"
hawk:budgetNotificationEmails:
  - "team@example.com"

When integrations are disabled, services fall back to simpler alternatives (CloudWatch instead of Datadog, no DNS delegation, etc.).