shoc-backend/terraform/README.md
Adam Moussa 25c2e84e8f feat(terraform): adopt live deployment roles safely
Add import-only dev and staging state with least-privilege HCP authentication and plan safety guards.
2026-08-28 19:12:34 -04:00

6 KiB

Terraform deployment infrastructure

The root module remains the isolated tf-poc Elastic Beanstalk and SQL Server stack in seahaven-external-dev (396287094661). It must never be reused for dev or staging state.

Live adoption is deliberately smaller. live/ imports only the existing GitHub Actions deploy roles and inline policies. All application, environment, database, certificate, DNS, network, runtime IAM, and secret resources remain external and are read only as inventory.

The Terraform roots are:

  • ./: isolated POC workload in shoc-backend-tf-poc.
  • bootstrap/: consolidated POC/dev/staging HCP role bootstrap in shoc-backend-bootstrap.
  • live/dev/: import-only dev deploy-role ownership in shoc-backend-dev.
  • live/staging/: import-only staging deploy-role ownership in shoc-backend-staging.

IAM lives in this repo, not org-baseline. App CD never runs a bootstrap root. Auto-apply stays off for every workspace.

The stabilized POC bootstrap deliberately leaves both POC HCP roles read only and removes ViewOnlyAccess plus the broad workload-mutation inline policy. The POC GitHub deploy role remains unchanged until a separately approved narrowing or teardown. Future POC teardown runs directly under the approved SSO administrator session, not the locked HCP apply role.

Locked names

  • Hostname: tf-poc.api.dev.seahaven.com (zone Z07671212N75U4YLPWZR8)
  • EB application / environment: shoc-backend-tf-poc
  • RDS identifier: shoc-backend-tf-poc
  • Catalog: shoc_tf_poc (create this database once after RDS is available)
  • Deploy role: arn:aws:iam::396287094661:role/tf-managed/githubdeploy-shoc-backend-tf-poc
  • GitHub Environment: tf-poc (OIDC environment:tf-poc)
  • HCP org seahaven, project seahaven-external-dev

HCP layout

All SHOC backend environments live in AWS account 396287094661 and HCP

The POC workspaces remain isolated until teardown. The live bootstrap owns hcptf-shoc-backend-{dev,staging} and matching -plan roles. The live environment workspaces own only their existing GitHub deploy role and inline policy. See live/README.md.

Console setup (once)

  1. In HCP Terraform, create project seahaven-external-dev if it does not exist.
  2. Create workspace shoc-backend-bootstrap:
    • VCS later, or CLI-driven until the branch is pushed
    • Working directory: terraform/bootstrap
    • Execution mode: Local
    • Auto-apply: off
  3. Create workspace shoc-backend-tf-poc:
    • Same branch
    • Working directory: terraform
    • Execution mode: Remote
    • Auto-apply: off
    • Speculative plans: on
  4. Do not use HCP "Quick setup AWS dynamic credentials".

Discovery (before first workload apply)

export AWS_PROFILE=seahaven-external-dev

aws elasticbeanstalk describe-environments \
  --environment-names shoc-backend-dev \
  --region us-east-1 \
  --query 'Environments[0].{Vpc:EndpointURL}'

aws elasticbeanstalk describe-configuration-settings \
  --application-name shoc-backend \
  --environment-name shoc-backend-dev \
  --region us-east-1 \
  --query "ConfigurationSettings[0].OptionSettings[?Namespace=='aws:ec2:vpc']"

Copy vpc-REPLACE_ME and subnet lists into a local terraform/terraform.tfvars (gitignored). Confirm:

  • app.terraform.io OIDC exists (create_tfc_oidc_provider=false in bootstrap). If the data source fails, set create_tfc_oidc_provider=true.
  • external-dev-execution-boundary exists.
  • aws-elasticbeanstalk-service-role exists.
  • GitHub OIDC provider token.actions.githubusercontent.com exists.

Bootstrap apply (Adam)

SSO AdministratorAccess in this account is subject to the external-dev SCP, so both hcptf-* roles set permissions_boundary to external-dev-execution-boundary.

export AWS_PROFILE=seahaven-external-dev
cd terraform/bootstrap
terraform login
terraform init
terraform apply

Then on workspace shoc-backend-tf-poc, set workspace-scoped env vars:

  • TFC_AWS_PROVIDER_AUTH=true
  • TFC_AWS_PLAN_ROLE_ARN = output hcp_plan_role_arn
  • TFC_AWS_APPLY_ROLE_ARN = output hcp_apply_role_arn

Never put those in a project variable set. Set sendgrid_api_key as a sensitive Terraform variable on that workspace when you want mail to work.

Workload apply

HCP Manual apply on shoc-backend-tf-poc. Confirm api.dev.seahaven.com still serves live before and after.

After RDS is available, create the catalog once (SQL Server Express does not accept db_name on aws_db_instance):

CREATE DATABASE [shoc_tf_poc];

Then first app deploy can run migrations into that catalog.

App deploy

GitHub Actions is the real CD path. .github/workflows/deploy.yml maps:

  • dev → dev
  • staging → staging
  • main → prod

The live roots import the roles already referenced by the dev and staging GitHub Environment secrets. The role ARNs do not change during adoption.

The POC local fallback remains available until teardown:

export AWS_PROFILE=seahaven-external-dev
bash scripts/deploy-api-tf.sh

The script refuses live names (shoc-backend-dev, api.dev.seahaven.com).

After POC confirmation

  1. Create shoc-backend-dev and shoc-backend-staging workspaces. Do not reuse POC state.
  2. Apply bootstrap/ only after approval to create the four narrowly scoped live HCP roles.
  3. Follow the no-op import and controlled-update sequence in live/README.md.
  4. Retire CDK deploy-role ownership only after both role imports are proven.
  5. Destroy the POC workload last. Bootstrap state remains.

Do not apply this module as environment=dev without imports.

Local CI equivalent

terraform -chdir=terraform fmt -check -recursive
terraform -chdir=terraform init -backend=false && terraform -chdir=terraform validate
terraform -chdir=terraform/bootstrap init -backend=false && terraform -chdir=terraform/bootstrap validate
terraform -chdir=terraform/live/dev init -backend=false && terraform -chdir=terraform/live/dev validate
terraform -chdir=terraform/live/staging init -backend=false && terraform -chdir=terraform/live/staging validate