Skip to content

Getting Started

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

Getting Started

The shortest path from an empty resource group to a running ReportMate. Read Prerequisites first — several required inputs have no defaults and the plan will not run without them.

1. Create the resource group and the state storage

The module does not create either.

az group create --name rg-reportmate-prod --location canadacentral

Then the state storage: a resource group, a storage account and a container.

az group create --name rg-terraform-state --location canadacentral
az storage account create --name examplestatestore --resource-group rg-terraform-state --sku Standard_LRS
az storage container create --name terraform-state --account-name examplestatestore

2. Write the backend config

Copy backend.tf.example to backend.tfvars and fill it in. The file is gitignored.

resource_group_name  = "rg-terraform-state"
storage_account_name = "examplestatestore"
container_name       = "terraform-state"
key                  = "reportmate.tfstate"

3. Write terraform.tfvars

Copy terraform.tfvars.example and set at least the eleven inputs that have no default. A minimum file looks like this — see Inputs for everything else.

resource_group_name = "rg-reportmate-prod"
location            = "canadacentral"

db_password = "REPLACE-WITH-A-GENERATED-PASSWORD"

container_registry_name  = "examplereportmateacr"
existing_registry_server = "ghcr.io/reportmate"
use_custom_registry      = false
frontend_image_name      = "web"

allowed_domains     = "example.com"
api_internal_secret = "REPLACE-WITH-A-GENERATED-SECRET"

key_vault_name     = "kv-reportmate-prod"
azure_ad_client_id = "00000000-0000-0000-0000-000000000000"
azure_ad_tenant_id = "00000000-0000-0000-0000-000000000000"

devops_resource_infrasec_group_object_id = "00000000-0000-0000-0000-000000000000"

The published images are ghcr.io/reportmate/web and ghcr.io/reportmate/reportmate-api. The module builds each image reference as <existing_registry_server>/<image name>:<tag>, and frontend_image_name defaults to reportmate, so set it to web when pulling from ghcr.io/reportmate. api_image_name already defaults to reportmate-api. Both container apps declare the registry with the managed identity as the credential.

Generate the two secrets rather than inventing them:

openssl rand -base64 32

4. Initialise and apply

terraform init -backend-config=backend.tfvars

Plan to a file, then apply that plan.

terraform plan -out=tfplan
terraform apply tfplan

The first apply creates the PostgreSQL server, the Container Apps environment and both container apps, the Key Vault and its secrets, Log Analytics and Application Insights, the managed identity and its role assignments, the maintenance job, and the Entra ID app registration. Front Door and the Function App are only created if you opted into them.

Expect it to take a while: PostgreSQL Flexible Server creation dominates, and the auth module contains a deliberate 60-second time_sleep waiting for Key Vault RBAC to propagate.

5. Find the URLs

The root module exposes only function-app and maintenance-job outputs (see Outputs), so the container app URLs come from the CLI:

az containerapp show --name reportmate-web-app-prod --resource-group rg-reportmate-prod --query properties.configuration.ingress.fqdn -o tsv
az containerapp show --name reportmate-functions-api --resource-group rg-reportmate-prod --query properties.configuration.ingress.fqdn -o tsv

6. Initialise the database schema

Terraform creates an empty database and applies no schema. The API container runs its Alembic migrations at startup, before it serves traffic, so the schema arrives with the first API revision. The database module's null_resource only prints a summary; the init-db curl command in that summary names an endpoint the API does not serve, so ignore it. The repo's schemas/ directory holds older migration SQL and the run-migrations.ps1 / run_migration.py helpers.

7. Point an agent at it

The API container app's ingress is external by design — Windows and macOS agents check in against it directly. Give the agents that URL and, if you set client_passphrases, the passphrase. The dashboard talks to the API over the Container Apps environment's internal DNS and never needs to be public for agents to work.

Optional: custom domain

Set enable_custom_domain = true and custom_domain_name, apply, then create the CNAME and _dnsauth TXT records the networking module's ssl_certificate_setup output describes. Certificate issuance is not instant. Turning this on also adds the custom domain to the API container's CORS allow-list and rewrites the dashboard's NEXTAUTH_URL, so the Entra ID redirect URI moves with it.

Clone this wiki locally