Info PPAAI App

AWS Infrastructure

How to deploy and manage the AWS infrastructure using OpenTofu

AWS Infrastructure (OpenTofu)

1. Bootstrap state backend (run once per AWS account/region)

This creates the S3 bucket and DynamoDB table used for OpenTofu/Terraform state and locking.

cd infra/bootstrap
tofu init
tofu apply

2. Local deployment

Create infra/.env (not committed) with your AWS credentials, for example:

AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=eu-central-1

TODO: Improve AWS credential handling

Long-term we should replace static access key/secret credentials with:

  • An AWS IAM role assumed via GitHub OIDC for CI deployments
  • AWS SSO / role-based auth (profiles) for local development

Then from the repo root:

make docker-build
make docker-push
make plan   # review the plan output
make apply  # confirm and apply

This will:

  • Build the app with Bun
  • Build and push the Docker image to ECR
  • Run tofu plan in infra/ (via make plan)
  • Run tofu apply in infra/ (via make apply)
  • Print the ALB DNS name after apply

3. Getting the ALB URL

After make apply finishes, the Makefile prints:

ALB DNS: <alb-dns-name>

You can open http://<alb-dns-name>/ in your browser.

4. Destroying infrastructure

To destroy the main application infrastructure (Aurora, ECS, ALB, VPC, etc.):

make infra-destroy

This runs tofu destroy in infra/.

If you also want to remove the state backend itself (S3 bucket and DynamoDB table), run this separately:

cd infra/bootstrap
tofu destroy

5. Initial setup gotcha (first deploy only)

NOTE: This section only applies to the first deploy. Future deploys can skip to step 3.

On the very first deploy, some infrastructure does not exist yet (S3 state bucket, DynamoDB lock table, and the ECR repository). The initial sequence should be:

  1. Bootstrap state backend:

    cd infra/bootstrap
    tofu init
    tofu apply
  2. Create core infra (VPC, ECS, ALB, Aurora, ECR, etc.):

    cd ../..
    make infra-init
    make plan
    make apply
  3. Build and push the Docker image (now that the ECR repo exists):

    make docker-build
    make docker-push
  4. Run make plan / make apply again to roll the ECS service to the newly pushed image.

After this first run, future deploys normally only need to rebuild/push and run make plan / make apply.

6. DNS / HTTPS stack (infra/dns)

The DNS stack manages the public subdomain ppaai.info.nl in Route 53 and is used for both DNS and TLS:

  • Creates a public hosted zone for ppaai.info.nl.
  • Is consumed by infra/dns_acm.tf to request ACM certificates for ppaai.info.nl and *.ppaai.info.nl with DNS validation.
  • Exposes the hosted zone name servers so you can delegate the subdomain from the parent info.nl zone (PowerDNS).

From the repo root:

make dns-init   # one-time backend init for infra/dns
make dns-plan   # optional
make dns-apply  # create/ update Route 53 hosted zone and ACM validation records

After make dns-apply, run:

cd infra/dns
tofu output ppaai_name_servers

Then add these NS records for ppaai.info.nl in your external DNS (PowerDNS) to delegate control of that subdomain to Route 53.

7. Email / SES stack (infra/email)

The email stack configures Amazon SES for the same subdomain and wires all required records into Route 53:

  • Creates an SES domain identity for ppaai.info.nl.
  • Adds the TXT verification record used by SES to verify the domain.
  • Enables DKIM and creates the three DKIM CNAME records in the ppaai.info.nl Route 53 zone.

From the repo root:

make email-init   # one-time backend init for infra/email
make email-plan   # optional
make email-apply  # SES domain identity + TXT + DKIM CNAMEs

After make email-apply, SES in eu-central-1 will list ppaai.info.nl as a verified domain. While your SES account is still in the sandbox, you must also verify any recipient email addresses (or request production access) before sending to them.

8. Testing SES email from the app

The Next.js app includes a small SES helper (apps/web/src/lib/email.ts) and a test route at POST /api/test-email that sends an email via SES.

To test this locally:

  1. Ensure SES (region eu-central-1) has:

    • Verified domain identity for ppaai.info.nl (via make email-apply).
    • Either a verified recipient email (for sandbox accounts) or production access enabled.
  2. In apps/web/.env set:

    AWS_REGION=eu-central-1
    EMAIL_FROM=no-reply@ppaai.info.nl
    TEST_EMAIL_TO=you@yourdomain.com
  3. Start the dev server from the repo root:

    bun run dev
  4. In another terminal, call the test route:

    curl -X POST http://localhost:3000/api/test-verification-email \
      -H 'Content-Type: application/json' \
      -d '{"to":"you@yourdomain.com"}'

    If to is omitted, the route uses TEST_EMAIL_TO from the environment.

If the response is { "ok": true } and SES does not reject the recipient, the SES integration is working. You can then reuse the same helper for registration and password-reset emails.