Skip to content

Getting Started

Rod Christiansen edited this page Oct 8, 2026 · 3 revisions

Getting Started

The shortest path from an empty AWS account to a running ReportMate. Read Prerequisites first.

1. Create the state backend

The root module's S3 backend needs a bucket and a lock table that Terraform cannot create for itself. The bootstrap/ module creates both, using local state, and is meant to be run once.

cd bootstrap
terraform init
terraform apply

Note the state_bucket and lock_table outputs.

2. Write the backend config

Copy the example and fill in the values from the bootstrap outputs.

cd ..
cp backend.hcl.example backend.hcl

backend.hcl is gitignored. The keys the backend needs are bucket, key, region, dynamodb_table, and encrypt.

3. Write your variables

cp terraform.tfvars.example terraform.tfvars

Fill in db_password, api_internal_secret, and client_passphrase — the example file ships them as CHANGE_ME. The example file does not include public_api_url, which is also required; add it. For the very first apply you can point it at a placeholder such as http://localhost:3000 and correct it in step 6.

For anything beyond a throwaway environment also set acm_certificate_arn, alert_email, and db_multi_az. Every variable is listed in Inputs.

4. Initialize and apply

terraform init -backend-config=backend.hcl
terraform plan
terraform apply

The apply takes a while; RDS is the long pole. Expect the ECS services to report zero healthy targets at this point — the ECR repositories are empty.

5. Push images

Log in to ECR for your account and region.

aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com

The API image is built by the reportmate-api repo's CI and published to GHCR, so deploying it is a mirror rather than a build.

docker pull --platform linux/amd64 ghcr.io/reportmate/reportmate-api:latest
docker tag ghcr.io/reportmate/reportmate-api:latest $(terraform output -raw ecr_api_repository_url):latest
docker push $(terraform output -raw ecr_api_repository_url):latest

For the frontend, build an image from reportmate-app-web, tag it against terraform output -raw ecr_frontend_repository_url, and push. The dashboard is also published as ghcr.io/reportmate/web (tagged latest and with each release tag) and can be mirrored the same way as the API. That image is built with the Dockerfile defaults, so NEXT_PUBLIC_DEMO_MODE is compiled in as false and NEXT_PUBLIC_API_BASE_URL as empty; build your own when you need other build-time values.

Then force a rollout on each service. The cluster is <project_name>-<environment>-cluster and the services are <project_name>-<environment>-api and <project_name>-<environment>-frontend.

aws ecs update-service --cluster <project>-<env>-cluster --service <project>-<env>-api --force-new-deployment --region <region>
aws ecs update-service --cluster <project>-<env>-cluster --service <project>-<env>-frontend --force-new-deployment --region <region>

6. Point the frontend at its real hostname

Read the ALB hostname out of the outputs.

terraform output frontend_url

Set public_api_url in terraform.tfvars to the hostname users will actually type — the ALB DNS name if you have no custom domain, or your own hostname if you put a CDN or a Route 53 record in front. Re-apply. This rewrites the frontend task definition and triggers a new deployment.

Getting this wrong is the most common first-deploy failure: the server-rendered HTML will contain API URLs pointing somewhere the browser cannot reach, and NextAuth will reject callbacks. See Troubleshooting.

7. Turn on TLS

Request an ACM certificate in the same region, set acm_certificate_arn, and apply. Until you do, the port 443 listener speaks plain HTTP while the port 80 listener unconditionally redirects to https:// — so a browser hitting port 80 ends up talking TLS to a non-TLS listener.

8. Configure sign-in

The module creates a Cognito user pool with two empty groups, admin and user, and stores the app client id and secret in Secrets Manager. It does not connect the dashboard to it. The dashboard's NextAuth configuration (lib/auth.ts in reportmate-app-web) offers Entra ID (azure-ad, the default when AUTH_PROVIDERS is unset), Google, and a credentials provider that rejects every login; it has no Cognito provider. The frontend task definition sets none of AUTH_PROVIDERS, AZURE_AD_CLIENT_ID, AZURE_AD_CLIENT_SECRET or AZURE_AD_TENANT_ID, so sign-in does not work until you add the settings for a provider the dashboard supports to the frontend task definition in modules/containers.

If you add a Cognito-backed provider yourself, set auth_callback_urls and auth_logout_urls to your real hostname — the defaults point at localhost:3000.

Convenience scripts

scripts/ contains deploy-api.sh, deploy-containers.sh, deploy-demo-loop.sh, and status.sh. They automate the steps above but were extracted from a monorepo and still carry hardcoded names and paths that will not match a fresh deployment. Read them before running them; see Troubleshooting.

What each one does:

  • deploy-api.sh [tag] pulls ghcr.io/reportmate/reportmate-api:<tag> (default latest), pushes it to the API ECR repository as both <tag> and latest, and forces a new deployment of the API service.
  • deploy-containers.sh [api|frontend|both] (default both) mirrors the API image the same way, reading the GHCR tag from API_GHCR_TAG (default latest) and pushing it under a timestamp tag plus latest. For the frontend it builds the dashboard with NEXT_PUBLIC_DEMO_MODE=true and pushes it the same way.
  • deploy-containers.sh builds the frontend from apps/www two directories above this repo (a monorepo path).
  • deploy-demo-loop.sh builds the demo-loop image from scripts/Dockerfile.demo-loop two directories above this repo (a monorepo path), pushes it, and rolls the demo-loop service.
  • status.sh prints the Terraform outputs, the ECS services with running and desired counts, and the RDS instances and ECR repositories whose names start with reportmate.

Each script pushes latest, which is what the task definitions run while api_image_tag and frontend_image_tag are left at their defaults.

Clone this wiki locally