Repository navigation
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.
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
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"
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
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.
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
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.
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.
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.
MIT licensed — reportmate.app