Skip to content

New Environment Setup

Prerequisites

  • AWS account with appropriate permissions
  • Pulumi CLI installed
  • AWS CLI configured
  • A domain name for your services
  • Sufficient Fargate/EC2 vCPU quotas — see sizing and quotas; request increases on day one, brand-new accounts are often denied

Quick Start

1. Install prerequisites

brew install pulumi awscli uv python@3.13 jq

Install Pulumi, uv, the AWS CLI, Python 3.13+, and jq.

2. Authenticate

aws sso login --profile your-profile
# or: aws configure

Default region

Set the default region to the region you'll deploy to (aws:region in your stack config, e.g. us-west-2) — the setup commands below create resources in that region. With an SSO profile, the profile's region applies; override with export AWS_DEFAULT_REGION=us-west-2 if it differs.

3. Set up Pulumi state backend

Create an S3 bucket and KMS key:

# Suffixing your account ID makes the bucket name globally unique
BUCKET="hawk-pulumi-state-$(aws sts get-caller-identity --query Account --output text)"
aws s3 mb "s3://$BUCKET" --region <region>
aws kms create-alias --alias-name alias/pulumi-secrets --region <region> \
  --target-key-id $(aws kms create-key --region <region> --query KeyMetadata.KeyId --output text)
pulumi login "s3://$BUCKET?region=<region>&awssdk=v2"
pulumi whoami -v   # Backend URL must show YOUR bucket before any stack operation

Credential troubleshooting

If pulumi login fails with NoCredentialProviders or AccessDenied: No AWSAccessKey was presented, your AWS credentials aren't visible to Pulumi. Make sure you ran aws configure (not just aws login). If using SSO profiles or short-lived credentials, ensure AWS_PROFILE is set, or export credentials explicitly. A failed login leaves Pulumi on the previous backend — verify pulumi whoami -v before any stack operation:

eval "$(aws configure export-credentials --format env)"

4. Choose a domain and DNS strategy

Hawk's services run under hawk.<hawk:domain> — for example, with hawk:domain: example.com the API is at api.hawk.example.com. Pulumi adds the hawk. segment automatically; do not include it in the config value. Each service is protected by an ACM TLS certificate. Pulumi needs a Route 53 public hosted zone for hawk:publicDomain and DNS delegation working before certs can validate. Pick one path before configuring the stack:

  • (a) Route 53 Domains — AWS handles registration, zone creation, and delegation. Leave hawk:createPublicZone: "false" (the default).
  • (b) Existing registrar + manual delegation — Create or import a Route 53 public hosted zone first, paste its four NS records into your registrar, and leave hawk:createPublicZone: "false" so preflight can validate delegation before deploy.

    # The zone is named after your hawk:publicDomain value (e.g. example.com) —
    # NOT hawk:domain; services live on hawk:domain subdomains INSIDE this zone
    ZID=$(aws route53 create-hosted-zone --name <your hawk:publicDomain> \
      --caller-reference "$(date +%s)" --query 'HostedZone.Id' --output text)
    aws route53 get-hosted-zone --id "$ZID" \
      --query 'DelegationSet.NameServers' --output text   # add these 4 NS records at your registrar
    
  • (c) Cloudflare delegation — Parent zone in Cloudflare; create or import the Route 53 public hosted zone first, then delegate the subdomain from Cloudflare before deploy. See the Configuration Reference for related setup.

  • (d) Skip TLS — testing only — Set hawk:skipTlsCerts: "true" for an HTTP-only deploy.

See the Configuration Reference: Domain & DNS for full details. Note: if DNS isn't working when pulumi up runs, the wildcard ACM cert validation will hang for ~75 min before failing — get delegation in place first and run preflight before deploy.

5. Create and deploy

cd infra
# Copy the example config FIRST — `pulumi stack init` merges its KMS metadata
# (secretsprovider/encryptedkey) into the existing file; copying afterwards
# would overwrite that metadata and break secret encryption.
cp ../Pulumi.example.yaml ../Pulumi.my-org.yaml
pulumi stack init my-org \
  --secrets-provider="awskms://alias/pulumi-secrets?region=<same as aws:region>&awssdk=v2"
# Edit Pulumi.my-org.yaml with your values after completing DNS delegation from step 4
docker login          # required — Docker Hub (https://hub.docker.com/) rate-limits anonymous pulls
docker login dhi.io   # required — Hawk's Python base images live on dhi.io (free Community tier; same Docker Hub credentials)
../scripts/dev/preflight.sh
pulumi up

First deploy creates ~400 AWS resources and takes about 15-20 minutes.

6. Secrets

For production stacks, set up API keys:

scripts/dev/set-api-keys.sh <env> OPENAI_API_KEY=sk-...

For dev environments, secrets are automatically shared from staging — no manual seeding needed.

To enable runners to clone private GitHub repos, configure the git credentials secret:

scripts/dev/set-git-config.sh <env> <github-pat>

The token is stored in Secrets Manager and readable by anyone with access. Use a fine-grained PAT with minimal scope.

7. Your services

After deployment, your services are available at:

  • API: https://api.hawk.<domain>
  • Viewer: https://viewer.hawk.<domain>
  • Middleman: middleman-ecs.<domain>

8. View API logs

aws logs tail "$(pulumi stack output api_log_group_name)" \
  --region us-west-2 --since 30m --format short | grep -v /health

Dev Environments

For development, create lightweight environments that share staging infrastructure:

./scripts/dev/new-dev-env.sh <your-name>    # e.g., ./scripts/dev/new-dev-env.sh alice

Requires PULUMI_BACKEND_URL to be exported and a deployed stg stack in that backend (the script clones its config). The script configures the stack and prompts to deploy. Your dev environment shares staging's VPC, ALB, and EKS cluster while getting its own database and services.

See Deployment for more details on managing dev environments.

Tailscale VPN Setup

If using Tailscale for private service access:

  1. Pick an IPv4 CIDR that doesn't conflict with existing ranges (e.g., 10.13.0.0/16)
  2. Add the CIDR to your Tailscale ACL rules
  3. Add a custom nameserver for <environment>.<your-domain> pointing to the CIDR's DNS resolver (e.g., 10.13.0.2)
  4. Set hawk:albInternal: "true" in your stack config
  5. Store a Tailscale auth key in AWS Secrets Manager
  6. Deploy with pulumi up